Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
tech.ydb.yoj.repository.test.RepositoryTest Maven / Gradle / Ivy
Go to download
Basic tests which all YOJ Repository implementations must pass.
package tech.ydb.yoj.repository.test;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import lombok.SneakyThrows;
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Test;
import tech.ydb.yoj.databind.expression.FilterExpression;
import tech.ydb.yoj.repository.BaseDb;
import tech.ydb.yoj.repository.db.Entity;
import tech.ydb.yoj.repository.db.IsolationLevel;
import tech.ydb.yoj.repository.db.Range;
import tech.ydb.yoj.repository.db.Repository;
import tech.ydb.yoj.repository.db.RepositoryTransaction;
import tech.ydb.yoj.repository.db.SchemaOperations;
import tech.ydb.yoj.repository.db.StdTxManager;
import tech.ydb.yoj.repository.db.Table;
import tech.ydb.yoj.repository.db.Tx;
import tech.ydb.yoj.repository.db.TxManager;
import tech.ydb.yoj.repository.db.TxOptions;
import tech.ydb.yoj.repository.db.exception.ConversionException;
import tech.ydb.yoj.repository.db.exception.DropTableException;
import tech.ydb.yoj.repository.db.exception.EntityAlreadyExistsException;
import tech.ydb.yoj.repository.db.exception.IllegalTransactionIsolationLevelException;
import tech.ydb.yoj.repository.db.exception.IllegalTransactionScanException;
import tech.ydb.yoj.repository.db.exception.OptimisticLockException;
import tech.ydb.yoj.repository.db.exception.UnavailableException;
import tech.ydb.yoj.repository.db.list.ListRequest;
import tech.ydb.yoj.repository.db.list.ListResult;
import tech.ydb.yoj.repository.db.readtable.ReadTableParams;
import tech.ydb.yoj.repository.test.entity.TestEntities;
import tech.ydb.yoj.repository.test.sample.TestDb;
import tech.ydb.yoj.repository.test.sample.TestDbImpl;
import tech.ydb.yoj.repository.test.sample.TestEntityOperations;
import tech.ydb.yoj.repository.test.sample.model.Book;
import tech.ydb.yoj.repository.test.sample.model.Bubble;
import tech.ydb.yoj.repository.test.sample.model.BytePkEntity;
import tech.ydb.yoj.repository.test.sample.model.Complex;
import tech.ydb.yoj.repository.test.sample.model.Complex.Id;
import tech.ydb.yoj.repository.test.sample.model.DetachedEntity;
import tech.ydb.yoj.repository.test.sample.model.DetachedEntityId;
import tech.ydb.yoj.repository.test.sample.model.EntityWithValidation;
import tech.ydb.yoj.repository.test.sample.model.IndexedEntity;
import tech.ydb.yoj.repository.test.sample.model.MultiLevelDirectory;
import tech.ydb.yoj.repository.test.sample.model.NetworkAppliance;
import tech.ydb.yoj.repository.test.sample.model.NonDeserializableEntity;
import tech.ydb.yoj.repository.test.sample.model.NonDeserializableObject;
import tech.ydb.yoj.repository.test.sample.model.Primitive;
import tech.ydb.yoj.repository.test.sample.model.Project;
import tech.ydb.yoj.repository.test.sample.model.Referring;
import tech.ydb.yoj.repository.test.sample.model.Simple;
import tech.ydb.yoj.repository.test.sample.model.Supabubble;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.A;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.B;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.Embedded;
import tech.ydb.yoj.repository.test.sample.model.UpdateFeedEntry;
import tech.ydb.yoj.repository.test.sample.model.Version;
import tech.ydb.yoj.repository.test.sample.model.VersionedAliasedEntity;
import tech.ydb.yoj.repository.test.sample.model.VersionedEntity;
import tech.ydb.yoj.repository.test.sample.model.WithUnflattenableField;
import tech.ydb.yoj.repository.test.sample.model.annotations.Sha256;
import tech.ydb.yoj.repository.test.sample.model.annotations.UniqueEntity;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.time.temporal.ChronoUnit.MILLIS;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static java.util.Spliterator.ORDERED;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static tech.ydb.yoj.repository.db.EntityExpressions.newFilterBuilder;
@SuppressWarnings("checkstyle:MethodCount")
public abstract class RepositoryTest extends RepositoryTestSupport {
protected TestDb db;
@Override
protected Repository createRepository() {
return TestEntities.init(createTestRepository());
}
@Override
public void setUp() {
super.setUp();
this.db = new TestDbImpl<>(this.repository);
}
@Override
public void tearDown() {
this.db = null;
super.tearDown();
}
protected abstract Repository createTestRepository();
@Test
public void schema() {
SchemaOperations schema = repository.schema(Simple.class);
schema.create();
schema.create(); // second create doesn't fail
schema.drop();
assertThatExceptionOfType(DropTableException.class).isThrownBy(schema::drop); // second drop fails
}
@Test
public void multiLevelDirectorySchema() {
SchemaOperations schema = repository.schema(MultiLevelDirectory.class);
schema.create();
}
@Test
public void snapshotWithSubfolders() {
TxManager txManager = new StdTxManager(repository);
checkEmpty(txManager);
String initSnapshotId = repository.makeSnapshot();
txManager.tx(() -> {
TestEntityOperations db = BaseDb.current(TestEntityOperations.class);
db.projects().save(new Project(new Project.Id("12312"), "ssss"));
db.projects().save(new Project(new Project.Id("123123"), "asa"));
db.table(Primitive.class).save(new Primitive(new Primitive.Id(121), 3));
db.table(Primitive.class).save(new Primitive(new Primitive.Id(122), 5));
});
checkNotEmpty(txManager);
// make ne snapshot and load initial
String snapshotWithDataId = repository.makeSnapshot();
repository.loadSnapshot(initSnapshotId);
// must be empty after load of initial snapshot
checkEmpty(txManager);
// load snapshot created after inserts and check entities present
repository.loadSnapshot(snapshotWithDataId);
checkNotEmpty(txManager);
}
private void checkNotEmpty(TxManager txManager) {
txManager.tx(() -> {
TestEntityOperations db = BaseDb.current(TestEntityOperations.class);
Assert.assertNotNull(db.projects().find(new Project.Id("12312")));
Assert.assertNotNull(db.projects().find(new Project.Id("123123")));
Assert.assertNotNull(db.table(Primitive.class).find(new Primitive.Id(121)));
Assert.assertNotNull(db.table(Primitive.class).find(new Primitive.Id(122)));
});
}
private void checkEmpty(TxManager txManager) {
txManager.tx(() -> {
TestEntityOperations db = BaseDb.current(TestEntityOperations.class);
Assert.assertEquals(0, db.projects().streamAllIds(10_000).count());
Assert.assertEquals(0, db.table(Primitive.class).streamAllIds(10_000).count());
});
}
@Test
public void listWithQueryByNullableField() {
Project projectWithName1 = db.tx(() -> db.projects().save(new Project(new Project.Id("1"), "named_1")));
assertEquals("named_1", projectWithName1.getName());
Project projectWithName2 = db.tx(() -> db.projects().save(new Project(new Project.Id("2"), "named_2")));
assertEquals("named_2", projectWithName2.getName());
Project projectWithoutName = db.tx(() -> db.projects().save(new Project(new Project.Id("3"), null)));
assertNull(projectWithoutName.getName());
assertEquals(3, db.readOnly().run(() -> db.projects().findAll()).size());
FilterExpression eqNotNullExp = newFilterBuilder(Project.class)
.where("name").eq("named_1")
.build();
testList(eqNotNullExp, projectWithName1);
FilterExpression neqNotNullExp = newFilterBuilder(Project.class)
.where("name").neq("named_1")
.build();
testList(neqNotNullExp, projectWithName2);
FilterExpression notNullExp = newFilterBuilder(Project.class)
.where("name").isNotNull()
.build();
testList(notNullExp, projectWithName1, projectWithName2);
FilterExpression nullExp = newFilterBuilder(Project.class)
.where("name").isNull()
.build();
testList(nullExp, projectWithoutName);
}
@Test
public void simpleCrudInDifferentTx() {
Project p1 = db.tx(() -> {
Project p = new Project(new Project.Id("1"), "named");
db.projects().save(p);
return p;
});
Project p2 = db.tx(() -> db.projects().find(new Project.Id("1")));
assertEquals(p1, p2);
Project p3 = db.tx(() -> {
Project p = new Project(new Project.Id("1"), "renamed");
db.projects().save(p);
return p;
});
Project p4 = db.tx(() -> db.projects().find(new Project.Id("1")));
assertEquals(p3, p4);
db.tx(() -> db.projects().delete(new Project.Id("1")));
Project p5 = db.tx(() -> db.projects().find(new Project.Id("1")));
assertNull(p5);
}
@Test
public void deferAfterCommitDontRunInDryRun() {
db.withDryRun(true).tx(
() -> Tx.Current.get().defer(
() -> Assert.fail("defer after commit musn't call in dry run")
)
);
}
@Test
public void deferNotInTxContext() {
db.tx(
() -> Tx.Current.get().defer(
() -> assertFalse(Tx.Current.exists())
)
);
}
@Test
public void deferFinallyDontRunInDryRun() {
db.withDryRun(true).tx(
() -> Tx.Current.get().deferFinally(
() -> Assert.fail("defer after commit musn't call in dry run")
)
);
}
@Test
public void deferFinallyCommit() {
AtomicInteger executions = new AtomicInteger();
db.tx(() -> Tx.Current.get().deferFinally(executions::incrementAndGet));
assertEquals(1, executions.get());
}
@Test
public void deferFinallyRollback() {
AtomicInteger executions = new AtomicInteger();
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> db.tx(() -> {
Tx.Current.get().deferFinally(executions::incrementAndGet);
throw new RuntimeException();
}));
assertEquals(1, executions.get());
}
@Test
public void deferFinallyRollbackRetryable() {
AtomicInteger executions = new AtomicInteger();
assertThatExceptionOfType(UnavailableException.class).isThrownBy(() -> db.tx(() -> {
Tx.Current.get().deferFinally(executions::incrementAndGet);
throw new OptimisticLockException("");
}));
assertEquals(1, executions.get());
}
@Test
public void deferFinallyNotInTxContext() {
db.tx(() -> Tx.Current.get().deferFinally(() -> assertFalse(Tx.Current.exists())));
}
@Test
public void deferFinallyRollbackNotInTxContext() {
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> db.tx(() -> {
Tx.Current.get().deferFinally(() -> assertFalse(Tx.Current.exists()));
throw new RuntimeException();
}));
}
@Test
public void findAll() {
List all = db.tx(() -> db.projects().findAll());
assertEquals(0, all.size());
Project p1 = db.tx(() -> {
Project p = new Project(new Project.Id("1"), "p1");
db.projects().save(p);
return p;
});
List all1 = db.tx(() -> db.projects().findAll());
assertEquals(1, all1.size());
assertEquals(p1, all1.get(0));
db.tx(() -> db.projects().save(new Project(new Project.Id("2"), "p2")));
List all2 = db.tx(() -> db.projects().findAll());
assertEquals(2, all2.size());
db.tx(() -> db.projects().deleteAll());
List all3 = db.tx(() -> db.projects().findAll());
assertEquals(0, all3.size());
}
@Test
public void streamAll() {
for (int i = 1; i < 5; i++) {
Project p = new Project(new Project.Id(String.valueOf(i)), "");
db.tx(() -> db.projects().save(p));
assertThat(db.tx(() -> db.projects().streamAll(2).collect(toList())))
.hasSize(i);
}
assertThat(db.tx(() -> db.projects().streamAll(2).limit(1).collect(toList())))
.hasSize(1);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.tx(() -> db.projects().streamAll(0)));
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.tx(() -> db.projects().streamAll(5001)));
}
private static > ReadTableParams defaultReadTableParamsNonLegacy() {
return RepositoryTest.buildReadTableParamsNonLegacy().build();
}
private static > ReadTableParams.ReadTableParamsBuilder buildReadTableParamsNonLegacy() {
return ReadTableParams.builder().useNewSpliterator(true);
}
@Test
public void readTable() {
assertThat(db.readOnly().run(() -> db.projects().readTable(defaultReadTableParamsNonLegacy()).count())).isEqualTo(0);
List expectedProjects = new ArrayList<>();
for (int i = 1; i <= 9; ++i) {
Project p = new Project(new Project.Id(String.valueOf(i)), "project-" + i);
expectedProjects.add(p);
db.tx(() -> db.projects().save(p));
assertThat(db.readOnly().run(() -> db.projects().readTable(defaultReadTableParamsNonLegacy()).count()))
.isEqualTo(i);
}
ReadTableParams readFrom = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjects.get(3).getId())
.rowLimit(5)
.ordered()
.build();
ReadTableParams readFromUnordered = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjects.get(3).getId())
.rowLimit(5)
.build();
assertThat(
db.readOnly().run(() -> db.projects().readTable(readFrom)
.map(p -> p.getId().getValue())
.collect(Collectors.toList()))
).isEqualTo(
expectedProjects.subList(3, 8).stream()
.map(p -> p.getId().getValue())
.collect(Collectors.toList())
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.readOnly().run(() -> db.projects().readTable(readFromUnordered)));
ReadTableParams readFromTo = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjects.get(3).getId())
.toKey(expectedProjects.get(7).getId())
.ordered()
.build();
ReadTableParams readFromToUnordered = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjects.get(3).getId())
.toKey(expectedProjects.get(7).getId())
.build();
assertThat(
db.readOnly().run(() -> db.projects().readTable(readFromTo)
.map(p -> p.getId().getValue())
.collect(Collectors.toList()))
).isEqualTo(
expectedProjects.subList(3, 7).stream()
.map(p -> p.getId().getValue())
.collect(Collectors.toList())
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.readOnly().run(() -> db.projects().readTable(readFromToUnordered)));
assertThatExceptionOfType(IllegalTransactionIsolationLevelException.class)
.isThrownBy(() -> db.tx(() -> db.projects().readTable(defaultReadTableParamsNonLegacy()).count()));
}
@Test
public void readTableIds() {
assertThat(db.readOnly().run(() -> db.projects().readTableIds(defaultReadTableParamsNonLegacy()).count()))
.isEqualTo(0);
List expectedProjectIds = new ArrayList<>();
for (int i = 1; i <= 9; ++i) {
Project p = new Project(new Project.Id(String.valueOf(i)), "project-" + i);
expectedProjectIds.add(p.getId());
db.tx(() -> db.projects().save(p));
assertThat(db.readOnly().run(() -> db.projects().readTableIds(defaultReadTableParamsNonLegacy()).count()))
.isEqualTo(i);
}
ReadTableParams readFrom = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjectIds.get(3))
.rowLimit(5)
.ordered()
.build();
ReadTableParams readFromUnordered = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjectIds.get(3))
.rowLimit(5)
.build();
assertThat(
db.readOnly().run(() -> db.projects().readTableIds(readFrom)
.map(Project.Id::getValue)
.collect(Collectors.toList()))
).isEqualTo(
expectedProjectIds.subList(3, 8).stream()
.map(Project.Id::getValue)
.collect(Collectors.toList())
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.readOnly().run(() -> db.projects().readTableIds(readFromUnordered)));
ReadTableParams readFromTo = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjectIds.get(3))
.toKey(expectedProjectIds.get(7))
.ordered()
.build();
ReadTableParams readFromToUnordered = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedProjectIds.get(3))
.toKey(expectedProjectIds.get(7))
.build();
assertThat(
db.readOnly().run(() -> db.projects().readTableIds(readFromTo)
.map(Project.Id::getValue)
.collect(Collectors.toList()))
).isEqualTo(
expectedProjectIds.subList(3, 7).stream()
.map(Project.Id::getValue)
.collect(Collectors.toList())
);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.readOnly().run(() -> db.projects().readTableIds(readFromToUnordered)));
assertThatExceptionOfType(IllegalTransactionIsolationLevelException.class)
.isThrownBy(() -> db.tx(() -> db.projects().readTableIds(defaultReadTableParamsNonLegacy()).count()));
}
@Test
public void readTableViews() {
assertThat(db.readOnly().run(() -> db.typeFreaks().readTableIds(defaultReadTableParamsNonLegacy()).count()))
.isEqualTo(0);
List expectedViews = new ArrayList<>();
int savedCount = 0;
for (int i = 0; i < 100; i++) {
TypeFreak tf = newTypeFreak(i, "AAA" + (i + 1), "bbb");
db.tx(() -> db.typeFreaks().save(tf));
savedCount++;
if (i < 50) {
expectedViews.add(new TypeFreak.View(tf.getId(), tf.getEmbedded()));
assertThat(db.readOnly().run(() -> db.typeFreaks()
.readTable(TypeFreak.View.class, defaultReadTableParamsNonLegacy()).count())).isEqualTo(savedCount);
}
}
ReadTableParams readFrom = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedViews.get(0).getId())
.rowLimit(expectedViews.size())
.ordered()
.build();
assertThat(db.readOnly().run(() -> db.typeFreaks().readTable(TypeFreak.View.class, readFrom).collect(toList())))
.isEqualTo(expectedViews);
ReadTableParams readFromTo = RepositoryTest.buildReadTableParamsNonLegacy()
.fromKeyInclusive(expectedViews.get(0).getId())
.toKeyInclusive(expectedViews.get(expectedViews.size() - 1).getId())
.ordered()
.build();
assertThat(db.readOnly().run(() -> db.typeFreaks().readTable(TypeFreak.View.class, readFromTo).collect(toList())))
.isEqualTo(expectedViews);
assertThatExceptionOfType(IllegalTransactionIsolationLevelException.class)
.isThrownBy(() -> db.tx(() -> db.typeFreaks().readTableIds(defaultReadTableParamsNonLegacy()).count()));
}
@Test
public void doNotCommitAfterTLI() {
Project.Id id1 = new Project.Id("id1");
Project.Id id2 = new Project.Id("id2");
RepositoryTransaction tx = repository.startTransaction(
TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE)
.withImmediateWrites(true)
.withFirstLevelCache(false)
);
tx.table(Project.class).find(id2);
db.tx(() -> db.projects().save(new Project(id2, "name2")));
tx.table(Project.class).save(new Project(id1, "name1")); // make tx available for TLI
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(() -> tx.table(Project.class).find(id2));
try {
tx.commit();
} catch (IllegalStateException ignore) {
// Some implementations throw, some don't
}
tx.rollback(); // YOJ-tx rollback is possible. session.rollbackCommit() won't execute
}
@Test
public void writeDontProduceTLI() {
Project.Id id = new Project.Id("id");
db.tx(() -> db.projects().save(new Project(id, "name")));
RepositoryTransaction tx = repository.startTransaction(
TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE)
.withImmediateWrites(true)
.withFirstLevelCache(false)
);
tx.table(Project.class).find(id);
db.tx(() -> {
db.projects().find(id);
db.projects().save(new Project(id, "name2"));
});
// write don't produce TLI
tx.table(Project.class).save(new Project(id, "name3"));
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(tx::commit);
}
@Test
public void testSpliteratorDoubleTryAdvance() {
db.tx(() -> {
for (int i = 0; i < 100; i++) {
db.projects().save(new Project(new Project.Id(String.valueOf(i)), ""));
}
});
db.readOnly().run(() -> {
Spliterator spliterator = db.projects().readTable(defaultReadTableParamsNonLegacy()).spliterator();
// this loop calls tryAdvance() on spliterator one time after tryAdvance() says false
while (true) {
if (!spliterator.tryAdvance(__ -> {})) {
return;
}
}
});
// one more example
db.readOnly().run(() -> {
Stream stream = db.projects().readTable(defaultReadTableParamsNonLegacy());
Stream> stream2 = StreamSupport.stream(
// With this line steam calls tryAdvance() on spliterator one time after tryAdvance() says false
() -> Iterables.partition(stream::iterator, 5).spliterator(),
ORDERED,
false
);
assertEquals(100, stream2.mapToLong(List::size).sum());
});
}
@Test
public void consistencyCheckAllColumnsOnFind() {
Project.Id id1 = new Project.Id("id1");
Project.Id id2 = new Project.Id("id2");
db.tx(() -> {
db.projects().save(new Project(id1, "name"));
db.projects().save(new Project(id2, "name"));
});
RepositoryTransaction tx = repository.startTransaction(
TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE)
.withImmediateWrites(true)
.withFirstLevelCache(false)
);
tx.table(Project.class).save(new Project(new Project.Id("id3"), "name")); // make tx available for TLI
tx.table(Project.class).find(id1);
tx.table(Project.class).find(id2);
db.tx(() -> {
db.projects().find(id2);
db.projects().save(new Project(id2, "name2"));
});
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(() -> tx.table(Project.class).find(id1));
}
@Test
public void streamAllWithPartitioning() {
db.tx(() -> {
db.complexes().insert(new Complex(new Complex.Id(0, 0L, "0", Complex.Status.OK)));
});
assertThat(db.tx(() -> {
ArrayList found = new ArrayList<>();
Iterators.partition(
db.complexes()
.streamAll(100)
.map(Complex::getId)
.iterator(), 100)
.forEachRemaining(found::addAll);
return found;
})).hasSize(1);
}
@Test
public void viewStreamAll() {
for (int i = 1; i < 5; i++) {
Book b = new Book(new Book.Id(String.valueOf(i)), i, "title-" + i, emptyList());
db.tx(() -> db.table(Book.class).save(b));
assertThat(db.tx(() -> db.table(Book.class).streamAll(Book.TitleViewId.class, 2).collect(toList())))
.hasSize(i);
}
db.tx(() -> db.table(Book.class)
.streamAll(Book.TitleViewId.class, 100)
.forEach(titleView -> assertThat(titleView.getTitle()).isNotBlank()));
assertThat(db.tx(() -> db.table(Book.class).streamAll(Book.TitleViewId.class, 2)
.limit(1).collect(toList())))
.hasSize(1);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.tx(() -> db.table(Book.class).streamAll(Book.TitleViewId.class, 0)));
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> db.tx(() -> db.table(Book.class).streamAll(Book.TitleViewId.class, 5001)));
}
@Test
public void streamAllComposite() {
db.tx(this::makeComplexes);
assertThat(
db.tx(() -> db.complexes().streamAll(2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().findAll())
);
}
@Test
public void streamEmpty() {
db.tx(() -> assertThat(db.complexes().streamAll(2)).isEmpty());
}
@Test
public void streamPartial() {
db.tx(this::makeComplexes);
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(0, 0L, "aaa", Complex.Status.OK), 2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", Complex.Status.OK))))
);
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(0, 0L, "aaa", null), 2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", null))))
);
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(0, 0L, null, null), 2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, null, null))))
);
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(0, null, null, null), 2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, null, null, null))))
);
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(null, null, null, null), 2).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(null, null, null, null))))
);
}
@Test
public void streamPartialWithPartitioning() {
db.tx(() -> {
db.complexes().insert(new Complex(new Complex.Id(0, 0L, "0", Complex.Status.OK)));
});
assertThat(
db.tx(() -> db.complexes().streamPartial(new Complex.Id(0, null, null, null), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(null, null, null, null))))
);
assertThat(db.tx(() -> {
ArrayList found = new ArrayList<>();
Iterators.partition(
db.complexes()
.streamPartial(new Complex.Id(0, null, null, null), 100)
.map(Complex::getId)
.iterator(), 100)
.forEachRemaining(found::addAll);
return found;
})).hasSize(1);
}
@Test
public void streamPartialIds() {
db.tx(this::makeComplexes);
assertThat(
db.tx(() -> db.complexes().streamPartialIds(new Complex.Id(0, 0L, "aaa", Complex.Status.OK), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", Complex.Status.OK)))
.stream().map(Complex::getId).collect(toList()))
);
assertThat(
db.tx(() -> db.complexes().streamPartialIds(new Complex.Id(0, 0L, "aaa", null), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", null)))
.stream().map(Complex::getId).collect(toList()))
);
assertThat(
db.tx(() -> db.complexes().streamPartialIds(new Complex.Id(0, 0L, null, null), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, 0L, null, null)))
.stream().map(Complex::getId).collect(toList()))
);
assertThat(
db.tx(() -> db.complexes().streamPartialIds(new Complex.Id(0, null, null, null), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(0, null, null, null)))
.stream().map(Complex::getId).collect(toList()))
);
assertThat(
db.tx(() -> db.complexes().streamPartialIds(new Complex.Id(null, null, null, null), 100).collect(toList()))
).isEqualTo(
db.tx(() -> db.complexes().find(Range.create(new Complex.Id(null, null, null, null)))
.stream().map(Complex::getId).collect(toList()))
);
}
@Test
public void countAll() {
long allSize = db.tx(() -> db.projects().countAll());
assertEquals(0, allSize);
db.tx(() -> db.projects().save(new Project(new Project.Id("1"), "p1")));
long all1 = db.tx(() -> db.projects().countAll());
assertEquals(1, all1);
db.tx(() -> db.projects().save(new Project(new Project.Id("2"), "p2")));
long all2 = db.tx(() -> db.projects().countAll());
assertEquals(2, all2);
db.tx(() -> db.projects().deleteAll());
long all3 = db.tx(() -> db.projects().countAll());
assertEquals(0, all3);
}
@Test
public void streamAllIterator() {
List projects = IntStream.range(100, 200)
.mapToObj(i -> new Project(new Project.Id("proj" + i), null))
.collect(toList());
db.tx(() -> db.projects().insertAll(projects));
db.tx(() -> {
List projectsViaStreamAllIterator = new ArrayList<>();
Iterator iterator = db.projects().streamAll(1_000).iterator();
while (iterator.hasNext()) {
projectsViaStreamAllIterator.add(iterator.next());
}
assertThat(projectsViaStreamAllIterator).isEqualTo(projects);
});
}
@Test
public void streamAllMultiPageIterator() {
List projects = IntStream.range(200, 300)
.mapToObj(i -> new Project(new Project.Id("p" + i), null))
.collect(toList());
db.tx(() -> db.projects().insertAll(projects));
db.tx(() -> {
List projectsViaStreamAllIterator = new ArrayList<>();
Iterator iterator = db.projects().streamAll(13).iterator();
while (iterator.hasNext()) {
projectsViaStreamAllIterator.add(iterator.next());
}
assertThat(projectsViaStreamAllIterator).isEqualTo(projects);
});
}
@Test
public void findRange() {
db.tx(this::makeComplexes);
db.tx(() -> {
assertEquals(1, db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", Complex.Status.OK))).size());
assertEquals(2, db.complexes().find(Range.create(new Complex.Id(0, 0L, "aaa", null))).size());
assertEquals(6, db.complexes().find(Range.create(new Complex.Id(0, 0L, null, null))).size());
assertEquals(18, db.complexes().find(Range.create(new Complex.Id(0, null, null, null))).size());
assertEquals(54, db.complexes().find(Range.create(new Complex.Id(null, null, null, null))).size());
assertEquals(4, db.complexes().find(Range.create(
new Complex.Id(0, 0L, "aaa", null),
new Complex.Id(0, 0L, "aab", null)
)).size());
assertEquals(4, db.complexes().find(Range.create(
new Complex.Id(0, 0L, null, null),
new Complex.Id(0, 0L, "aab", null)
)).size());
assertEquals(2, db.complexes().find(Range.create(
new Complex.Id(0, 0L, "bbb", null),
new Complex.Id(0, 0L, null, null)
)).size());
assertEquals(36, db.complexes().find(Range.create(
new Complex.Id(1, null, null, null),
new Complex.Id(2, null, null, null)
)).size());
});
}
@Test
public void findPartialKeyParallelTransactions() {
Complex.Id partialId = new Complex.Id(100, 200L, "foo", null);
RepositoryTransaction tx1 = startTransaction();
RepositoryTransaction tx2 = startTransaction();
tx1.table(Complex.class).find(Set.of(partialId));
tx2.table(Complex.class).find(Set.of(partialId));
tx1.table(Complex.class).save(new Complex(partialId.withD(Complex.Status.OK)));
tx2.table(Complex.class).save(new Complex(partialId.withD(Complex.Status.FAIL)));
tx1.commit();
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(tx2::commit);
}
private RepositoryTransaction startTransaction() {
return repository.startTransaction(TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE));
}
protected void makeComplexes() {
for (int a = 0; a < 3; a++) {
for (long b = 0; b < 3; b++) {
for (String c : asList("aaa", "aab", "bbb")) {
for (Complex.Status d : Complex.Status.values()) {
db.complexes().insert(new Complex(new Complex.Id(a, b, c, d)));
}
}
}
}
}
@Test
public void findInCompleteIdAllFromCache() {
db.tx(this::makeComplexes);
db.tx(() -> {
var idsToFetch = ImmutableSet.of(
new Id(0, 0L, "aaa", Complex.Status.OK),
new Id(0, 0L, "aaa", Complex.Status.FAIL)
);
var firstQueryResult = db.complexes().find(idsToFetch).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(2, firstQueryResult.size());
var complexesSecondQuery = db.complexes().find(idsToFetch).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(2, complexesSecondQuery.size());
for (var pair : firstQueryResult.entrySet()) {
assertSame(
pair.getValue(),
complexesSecondQuery.get(pair.getKey())
);
}
});
}
@Test
public void findInCompleteIds() {
/*
Test that find(Set<>) with complete Ids:
* checks in cache first (so newly inserted and updated values will be actual)
* if some ids not found in cache — fetches rest from db
* returns union of entries collected from cache and db
* (we cannot test cache empty values here, so somewhere else) correctly populates cache with new values
*/
var idInDbOnly = new Id(0, 0L, "aaa", Complex.Status.OK);
var idInDbAndCache = new Id(0, 1L, "aaa", Complex.Status.OK);
var idInCacheOnly = new Id(0, 3L, "aaa", Complex.Status.OK);
var idUnexistent = new Id(0, -1L, "aaa", Complex.Status.OK);
var idUnexistentMarkedEmptyInCache = new Id(0, -2L, "aaa", Complex.Status.OK);
db.tx(this::makeComplexes);
db.tx(() -> {
var entryInDbAndCache = db.complexes().find(idInDbAndCache);
assertNotNull(entryInDbAndCache);
assertNull(db.complexes().find(idUnexistentMarkedEmptyInCache));
var entryInCache = new Complex(idInCacheOnly);
db.complexes().insert(entryInCache);
var result = db.complexes().find(ImmutableSet.of(
idInDbOnly,
idInDbAndCache,
idInCacheOnly,
idUnexistent,
idUnexistentMarkedEmptyInCache
)).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(3, result.size());
// is same because it is from cache
assertSame(entryInDbAndCache, result.get(idInDbAndCache));
// is same because it is from cache (not inserted to db for now)
assertSame(entryInCache, result.get(idInCacheOnly));
// this fetched from db and should exist
assertNotNull(result.get(idInDbOnly));
});
}
@Test
public void findInPartialIds() {
/*
Test that find(Set<>) with partial Ids:
* queries db
* checks resulted entries existence in cache by complete id (so entries modified in same tx would be actual)
* returns union of non-empty cached matched from result and non-matched from db
* correctly updates cache with values found in db
* (NOT IMPLEMENTED) also newly created should be searched in cache and fetched
*/
var idInDbOnly = new Id(0, 0L, "aaa", Complex.Status.OK);
var idInDbAndUpdatedInCache = new Id(0, 1L, "aaa", Complex.Status.OK);
// newly created (only in cache) NOT IMPLEMENTED yet
// var idInCacheOnly = new Id(0, 3L, "aaa", Complex.Status.OK);
var idUnexistentMarkedEmptyInCache = new Id(0, -2L, "aaa", Complex.Status.OK);
var createdEntries = new HashMap();
db.tx(() -> {
var entryOne = db.complexes().insert(new Complex(idInDbOnly));
var entryTwo = db.complexes().insert(new Complex(idInDbAndUpdatedInCache));
createdEntries.put(entryOne.getId(), entryOne);
createdEntries.put(entryTwo.getId(), entryTwo);
});
db.tx(() -> {
var updatedEntry = new Complex(idInDbAndUpdatedInCache, "UPDATED");
db.complexes().save(updatedEntry);
assertNull(db.complexes().find(idUnexistentMarkedEmptyInCache));
var result = db.complexes().find(ImmutableSet.of(
new Id(0, null, null, null)
)).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(2, result.size());
// is same because it is from cache
assertSame(updatedEntry, result.get(idInDbAndUpdatedInCache));
// fetched from db, not cache
var entryIdInDbOnly = result.get(idInDbOnly);
assertEquals(createdEntries.get(idInDbOnly), entryIdInDbOnly);
assertNotSame(createdEntries.get(idInDbOnly), entryIdInDbOnly);
// but in cache now (why not?)
assertSame(entryIdInDbOnly, db.complexes().find(idInDbOnly));
});
}
@Test
public void findInCorrectSort() {
var idA = new Id(0, 0L, "aaa", Complex.Status.OK);
var idB = new Id(0, 0L, "bbb", Complex.Status.OK);
var idC = new Id(0, 0L, "ccc", Complex.Status.OK);
db.tx(() -> {
db.complexes().insert(new Complex(idA, "A"));
db.complexes().insert(new Complex(idB, "B"));
db.complexes().insert(new Complex(idC, "C"));
});
db.tx(() -> {
assertEquals(
db.complexes().find(ImmutableSet.of(idA, idB, idC)).stream().map(Entity::getId).collect(toList()),
List.of(idA, idB, idC)
);
// put in cache
db.complexes().find(idB);
assertEquals(
db.complexes().find(ImmutableSet.of(idA, idB, idC)).stream().map(Entity::getId).collect(toList()),
List.of(idA, idB, idC)
);
});
}
@Test
public void findInConsiderDeleted() {
var firstId = new Id(0, 0L, "aaa", Complex.Status.OK);
var secondId = new Id(0, 1L, "aaa", Complex.Status.OK);
db.tx(() -> {
db.complexes().insert(new Complex(firstId));
db.complexes().insert(new Complex(secondId));
});
db.tx(() -> {
var result = db.complexes().find(ImmutableSet.of(
firstId,
secondId
)).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(2, result.size());
db.complexes().delete(firstId);
result = db.complexes().find(ImmutableSet.of(
firstId,
secondId
)).stream().collect(
Collectors.toMap(Complex::getId, Function.identity())
);
assertEquals(1, result.size());
});
}
@Test
public void findInParallelTx() {
var id = new Id(0, 0L, "aaa", Complex.Status.OK);
RepositoryTransaction tx0 = startTransaction();
tx0.table(Complex.class).save(new Complex(id));
tx0.commit();
RepositoryTransaction tx1 = startTransaction();
tx1.table(Complex.class).find(id);
tx1.table(Complex.class).save(new Complex(id, "updated"));
RepositoryTransaction tx2 = startTransaction();
tx2.table(Complex.class).delete(id);
var found = tx1.table(Complex.class).find(ImmutableSet.of(
new Id(0, null, null, null)
));
tx2.commit();
assertNotNull(found);
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(tx1::commit);
}
private int getComplexIdA(int index) {
return index / 2;
}
private Complex.Id getComplexId(int index) {
var a = getComplexIdA(index);
var status = index % 2 == 0 ? Complex.Status.OK : Complex.Status.FAIL;
return new Complex.Id(a, (long) index, String.valueOf(index), status);
}
private String getComplexValue(int index) {
var id = getComplexId(index);
return "%s-%s-%s-%s".formatted(id.getD(), id.getC(), id.getB(), id.getA());
}
private Complex getComplex(int index) {
return new Complex(getComplexId(index), getComplexValue(index));
}
private void fillComplexTableForFindIn() {
db.tx(() -> IntStream.range(0, 4).mapToObj(this::getComplex).forEach(db.complexes()::save));
}
@Test
public void findInIdsFilteredAndOrdered() {
var ids = IntStream.range(0, 6).mapToObj(this::getComplexId).collect(toSet());
findInIdsFilteredAndOrdered(ids);
}
@Test
public void findInPrefixedIdsFilteredAndOrdered() {
var ids = IntStream.range(0, 6)
.mapToObj(i -> new Complex.Id(getComplexIdA(i), null, null, null))
.collect(toSet());
findInIdsFilteredAndOrdered(ids);
}
private void findInIdsFilteredAndOrdered(Set ids) {
fillComplexTableForFindIn();
var actual = db.tx(() -> db.complexes().query()
.ids(ids)
.filter(fb -> fb.where("id.d").eq(Complex.Status.OK))
.orderBy(ob -> ob.orderBy("value").descending())
.find()
);
assertThat(actual).containsExactly(getComplex(2), getComplex(0));
}
@Test
public void findInIdsViewFilteredAndOrdered() {
var ids = IntStream.range(0, 6).mapToObj(this::getComplexId).collect(toSet());
findInIdsViewFilteredAndOrdered(ids);
}
@Test
public void findInPrefixedIdsViewFilteredAndOrdered() {
var ids = IntStream.of(1, 3, 5)
.mapToObj(i -> new Complex.Id(getComplexIdA(i), null, null, null))
.collect(toSet());
findInIdsViewFilteredAndOrdered(ids);
}
public void findInIdsViewFilteredAndOrdered(Set ids) {
fillComplexTableForFindIn();
var actual = db.tx(() -> db.complexes().query()
.ids(ids)
.filter(fb -> fb.where("id.d").eq(Complex.Status.OK))
.orderBy(ob -> ob.orderBy("value").descending())
.find(Complex.View.class)
);
assertThat(actual).containsExactly(
new Complex.View(getComplexValue(2)),
new Complex.View(getComplexValue(0))
);
}
private String getIndexedEntityId(int index) {
return "id-" + index;
}
private String getIndexedEntityKey(int index) {
return "key-" + index;
}
private String getIndexedEntityValue(int index) {
return "v-" + (index / 3);
}
private String getIndexedEntityValue2(int index) {
return "v2-" + (index % 3);
}
private IndexedEntity getIndexedEntity(int index) {
return new IndexedEntity(
new IndexedEntity.Id(getIndexedEntityId(index)),
getIndexedEntityKey(index),
getIndexedEntityValue(index),
getIndexedEntityValue2(index)
);
}
private void fillIndexedTableForFindIn() {
db.tx(() -> IntStream.range(0, 8).mapToObj(this::getIndexedEntity).forEach(db.indexedTable()::save));
}
@Test
public void findInKeysFilteredAndOrdered() {
findInKeysFilteredAndOrdered(false);
}
@Test
public void findInKeysFilteredAndOrderedLimited() {
findInKeysFilteredAndOrdered(true);
}
@Test
public void findInPrefixedKeysFilteredAndOrdered() {
findInPrefixedKeysFilteredAndOrdered(false);
}
@Test
public void findInPrefixedKeysFilteredAndOrderedLimited() {
findInPrefixedKeysFilteredAndOrdered(true);
}
public void findInKeysFilteredAndOrdered(boolean limited) {
var keys = IntStream.range(0, 10)
.mapToObj(i -> new IndexedEntity.Key(getIndexedEntityValue(i), getIndexedEntityValue2(i)))
.collect(toSet());
findInKeysFilteredAndOrdered(keys, limited);
}
private void findInPrefixedKeysFilteredAndOrdered(boolean limited) {
var keys = IntStream.range(0, 10)
.mapToObj(i -> new IndexedEntity.Key(getIndexedEntityValue(i), null))
.collect(toSet());
findInKeysFilteredAndOrdered(keys, limited);
}
private void findInKeysFilteredAndOrdered(Set keys, boolean limited) {
fillIndexedTableForFindIn();
var actual = db.tx(() -> {
var queryBuilder = db.indexedTable().query()
.index(IndexedEntity.VALUE_INDEX)
.keys(keys)
.where("valueId2").neq(getIndexedEntityValue2(1))
.orderBy(ob -> ob
.orderBy("valueId2").descending()
.orderBy("valueId").ascending()
);
if (limited) {
queryBuilder = queryBuilder.limit(3);
}
return queryBuilder.find();
});
assertThat(actual).containsExactlyElementsOf(
IntStream.of(2 /*3*0+2*/, 5 /*3*1+2*/, 0 /*3*0+0*/, 3 /*3*1+0*/, 6 /*3*2+0*/)
.limit(limited ? 3 : 10)
.mapToObj(this::getIndexedEntity)
.toList()
);
}
@Test
public void findInKeysViewFilteredAndOrdered() {
findInKeysViewFilteredAndOrdered(false);
}
@Test
public void findInKeysViewFilteredAndOrderedLimited() {
findInKeysViewFilteredAndOrdered(true);
}
@Test
public void findInPrefixedKeysViewFilteredAndOrdered() {
findInPrefixedKeysViewFilteredAndOrdered(false);
}
@Test
public void findInPrefixedKeysViewFilteredAndOrderedLimited() {
findInPrefixedKeysViewFilteredAndOrdered(true);
}
@Test
public void findViewDistinct() {
fillIndexedTableForFindIn();
var actualViews = db.tx(() -> db.indexedTable().query().find(IndexedEntity.ValueIdView.class, true));
var actualValueIds = actualViews.stream()
.map(IndexedEntity.ValueIdView::getValueId)
.collect(toSet());
assertEquals(Set.of("v-0", "v-1", "v-2"), actualValueIds);
}
private void findInKeysViewFilteredAndOrdered(boolean limited) {
var keys = IntStream.range(0, 10)
.mapToObj(i -> new IndexedEntity.Key(getIndexedEntityValue(i), getIndexedEntityValue2(i)))
.collect(toSet());
findInKeysViewFilteredAndOrdered(keys, limited);
}
private void findInPrefixedKeysViewFilteredAndOrdered(boolean limited) {
var keys = IntStream.range(0, 10)
.mapToObj(i -> new IndexedEntity.Key(getIndexedEntityValue(i), null))
.collect(toSet());
findInKeysViewFilteredAndOrdered(keys, limited);
}
private void findInKeysViewFilteredAndOrdered(Set keys, boolean limited) {
fillIndexedTableForFindIn();
var actual = db.tx(() -> {
var queryBuilder = db.indexedTable().query()
.index(IndexedEntity.VALUE_INDEX)
.keys(keys)
.where("valueId2").neq(getIndexedEntityValue2(2))
.orderBy(ob -> ob.orderBy("id.versionId").descending());
if (limited) {
queryBuilder = queryBuilder.limit(3);
}
return queryBuilder.find(IndexedEntity.View.class);
});
assertThat(actual).containsExactlyElementsOf(
IntStream.of(7 /*3*2+1*/, 6 /*3*2+0*/, 4 /*3*1+1*/, 3 /*3*1+0*/, 1 /*3*0+1*/, 0 /*3*0+0*/)
.limit(limited ? 3 : 10)
.mapToObj(i -> new IndexedEntity.View(getIndexedEntityId(i)))
.toList()
);
}
@Test
public void doubleTxIsOk() {
db.tx(this::findRange);
}
@Test(expected = IllegalArgumentException.class)
public void findPartialId() {
db.tx(() -> db.bubbles().find(new Bubble.Id("b", null)));
}
@Test
public void fixSnapshotVersionOnFirstQuery() {
var projectId = new Project.Id("value");
db.tx(() -> db.tx(() -> db.projects().insert(new Project(projectId, "oldName"))));
String newName = "name";
Project project = db.tx(() -> {
db.separate().tx(() -> db.projects().save(new Project(projectId, newName)));
return db.projects().find(projectId);
});
assertThat(project.getName()).isEqualTo(newName);
}
@Test
public void dontCommitOnUserError() {
var projectId = new Project.Id("value");
try {
db.delayedWrites().tx(() -> {
db.projects().save(new Project(projectId, "name"));
throw new RuntimeException("");
});
} catch (RuntimeException ignore) {
}
assertThat(db.tx(() -> db.projects().find(projectId))).isNull();
try {
db.immediateWrites().tx(() -> {
db.projects().save(new Project(projectId, "name"));
throw new RuntimeException("");
});
} catch (RuntimeException ignore) {
}
assertThat(db.tx(() -> db.projects().find(projectId))).isNull();
}
@Test
public void immediateWrites() {
db.delayedWrites().noFirstLevelCache().tx(() -> {
var projectId = new Project.Id("value1");
assertThat(db.projects().find(projectId)).isNull();
db.projects().save(new Project(projectId, "name"));
assertThat(db.projects().find(projectId)).isNull();
});
db.immediateWrites().noFirstLevelCache().tx(() -> {
var projectId = new Project.Id("value2");
var name = "name";
assertThat(db.projects().find(projectId)).isNull();
db.projects().save(new Project(projectId, name));
assertThat(db.projects().find(projectId)).isEqualTo(new Project(projectId, name));
});
}
@Test
public void snapshotReadWithoutTli() {
var projectId = new Project.Id("value");
String newName = "name";
db.tx(() -> db.tx(() -> db.projects().insert(new Project(projectId, newName))));
Project project = db.tx(() -> {
Project findedProject = db.projects().find(projectId);
db.separate().tx(() -> db.projects().save(new Project(projectId, "invisible")));
return findedProject;
});
assertThat(project.getName()).isEqualTo(newName);
assertThat(db.tx(() -> db.projects().find(projectId)).getName()).isEqualTo("invisible");
project = db.tx(() -> {
db.separate().tx(() -> db.projects().save(new Project(projectId, "invisible")));
db.projects().find(projectId);
return db.projects().save(new Project(projectId, newName));
});
assertThat(project.getName()).isEqualTo(newName);
}
@Test
public void findByPredicate() {
db.tx(() -> {
db.projects().insert(
new Project(new Project.Id("unnamed-p1"), null),
new Project(new Project.Id("named-p2"), "P2"),
new Project(new Project.Id("named-p3"), "P3")
);
db.typeFreaks().insert(
newTypeFreak(0, "AAA1", "bbb"),
newTypeFreak(1, "AAA2", "bbb"),
newTypeFreak(2, "AAA3", "bbb"),
newTypeFreak(3, "AAA4", "bbb"),
newTypeFreak(4, "AAA5", "ccc"),
newTypeFreak(5, "AAA6", "ccc"),
newTypeFreak(6, "AAA7", "ccc"),
newTypeFreak(7, "AAA8", "ccc")
);
});
db.tx(() -> {
assertThat(db.projects().findNamed()).containsExactlyInAnyOrder(
new Project(new Project.Id("named-p3"), "P3"),
new Project(new Project.Id("named-p2"), "P2")
);
assertThat(db.typeFreaks().findWithEmbeddedAIn("AAA1", "AAA3", "AAA7", "AAA8")).containsExactlyInAnyOrder(
newTypeFreak(0, "AAA1", "bbb"),
newTypeFreak(2, "AAA3", "bbb"),
newTypeFreak(6, "AAA7", "ccc"),
newTypeFreak(7, "AAA8", "ccc")
);
assertThat(db.typeFreaks().findWithEmbeddedBNotEqualTo("bbb")).containsExactlyInAnyOrder(
newTypeFreak(4, "AAA5", "ccc"),
newTypeFreak(5, "AAA6", "ccc"),
newTypeFreak(6, "AAA7", "ccc"),
newTypeFreak(7, "AAA8", "ccc")
);
assertThat(db.typeFreaks().findWithEmbeddedANotIn(asList("AAA1", "AAA3", "AAA7", "AAA8"))).containsExactlyInAnyOrder(
newTypeFreak(1, "AAA2", "bbb"),
newTypeFreak(3, "AAA4", "bbb"),
newTypeFreak(4, "AAA5", "ccc"),
newTypeFreak(5, "AAA6", "ccc")
);
});
}
@Test
public void findByPredicateWithLimit() {
List named = IntStream.range(0, 100)
.mapToObj(i -> new Project(new Project.Id("named-p" + i), "P" + i))
.collect(toList());
db.tx(() -> {
db.projects().insert(new Project(new Project.Id("unnamed-p1"), null));
db.projects().insertAll(named);
});
db.tx(() -> {
final List topNamed = db.projects().findTopNamed(10);
assertThat(topNamed).hasSize(10);
});
}
protected TypeFreak newTypeFreak(int n, String a, String b) {
return new TypeFreak(new TypeFreak.Id("tf", n),
true,
(byte) 1,
(byte) 3,
(short) 2,
1 << 30,
1L << 50,
1.5f,
0.5,
true,
(byte) 1,
(byte) 255,
(short) 2,
1 << 30,
1L << 50,
1.5f,
0.5,
"\uD83D\uDE09 \u26FE \u262D \u2603 \uD83D\uDCBB",
"some string",
"byte string".getBytes(),
TypeFreak.Status.OK,
TypeFreak.Status.DRAFT,
new Embedded(new A(a), new B(b)),
new Embedded(new A(b), new B(a)),
new Embedded(new A(b), new B(a)),
new Embedded(new A(b), new B(a)),
new Embedded(new A(b), new B(a)),
Instant.parse("2018-02-05T14:36:05.960Z"),
asList("1", "2", "3"),
asList(new Embedded(new A("aaa"), new B("bbb")), new Embedded(new A("xxx"), new B("yyy"))),
singleton(new Embedded(new A("aaa"), new B("bbb"))),
singletonMap(1, new Embedded(new A("mmm"), new B("nnn"))),
singletonMap(1, new Embedded(new A("nnn"), new B("ooo"))),
singletonMap(1, new Embedded(new A("ooo"), new B("ppp"))),
singletonMap(1, new Embedded(new A("ppp"), new B("qqq"))),
new TypeFreak.StringValueWrapper("the string value wrapper"),
"hi there",
new TypeFreak.Ticket("CLOUD", 25)
);
}
@Test
public void simpleCrudInTheSameTx() {
db.tx(() -> {
Project p1 = new Project(new Project.Id("1"), "named");
db.projects().save(p1); // save() is pending until end of tx
Project p2 = db.projects().find(new Project.Id("1"));
assertEquals(new Project(new Project.Id("1"), "named"), p2);
Project p3 = new Project(new Project.Id("1"), "renamed");
db.projects().save(p3); // save() is pending
Project p4 = db.projects().find(new Project.Id("1"));
assertEquals(new Project(new Project.Id("1"), "renamed"), p4);
});
db.tx(() -> {
Project p1 = db.projects().find(new Project.Id("1"));
assertEquals(new Project(new Project.Id("1"), "renamed"), p1);
db.projects().delete(new Project.Id("1")); // delete() is pending
Project p5 = db.projects().find(new Project.Id("1"));
assertNull(p5); // now it's deleted
});
db.tx(() -> {
Project p1 = db.projects().find(new Project.Id("1"));
assertNull(p1); // now it's deleted
});
}
@Test
public void optimisticLockWrite() {
RepositoryTransaction tx1 = startTransaction();
tx1.table(Project.class).save(new Project(new Project.Id("1"), "x"));
tx1.commit();
RepositoryTransaction tx2 = startTransaction();
RepositoryTransaction tx3 = startTransaction();
Project p2 = tx2.table(Project.class).find(new Project.Id("1"));
Project p3 = tx3.table(Project.class).find(new Project.Id("1"));
tx2.table(Project.class).save(new Project(new Project.Id("1"), p2.getName() + "y"));
tx3.table(Project.class).save(new Project(new Project.Id("1"), p3.getName() + "z"));
tx2.commit();
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(tx3::commit);
Project p4 = db.tx(() -> db.table(Project.class).find(new Project.Id("1")));
assertEquals(new Project(new Project.Id("1"), p2.getName() + "y"), p4);
}
@Test
public void optimisticLockRead() {
RepositoryTransaction tx0 = startTransaction();
tx0.table(Project.class).save(new Project(new Project.Id("1"), "p1"));
tx0.table(Project.class).save(new Project(new Project.Id("2"), "p2"));
tx0.commit();
RepositoryTransaction tx1 = startTransaction();
tx1.table(Project.class).insert(new Project(new Project.Id("3"), "p3"));
tx1.table(Project.class).find(new Project.Id("1"));
{
RepositoryTransaction tx2 = startTransaction();
tx2.table(Project.class).save(new Project(new Project.Id("1"), "p1-1"));
tx2.commit();
}
// read object was touched -> rollback on any operation
//не prepare запросы валятся при обращении
//prepare запросы валятся на комите
assertThatExceptionOfType(OptimisticLockException.class)
.isThrownBy(() -> {
//не prepare запросы валятся при обращении
try {
tx1.table(Project.class).find(new Project.Id("1"));
} catch (Exception e) {
tx1.rollback();
throw e;
}
//prepare запросы валятся на комите
tx1.commit();
});
}
@Test
public void concurrentWriteIsOk() {
RepositoryTransaction tx1 = startTransaction();
RepositoryTransaction tx2 = startTransaction();
tx1.table(Project.class).save(new Project(new Project.Id("1"), "x"));
tx2.table(Project.class).save(new Project(new Project.Id("1"), "y"));
tx1.commit();
tx2.commit();
assertEquals(new Project(new Project.Id("1"), "y"), db.tx(() -> db.table(Project.class).find(new Project.Id("1"))));
}
@Test
public void concurrentReadIsOk() {
RepositoryTransaction tx0 = startTransaction();
tx0.table(Project.class).save(new Project(new Project.Id("1"), "p1"));
tx0.commit();
RepositoryTransaction tx1 = startTransaction();
RepositoryTransaction tx2 = startTransaction();
tx1.table(Project.class).find(new Project.Id("1"));
tx2.table(Project.class).find(new Project.Id("1"));
tx1.commit();
tx2.commit();
}
@Test
public void allTypes() {
TypeFreak t1 = db.tx(() -> db.typeFreaks().save(newTypeFreak(1, "aaa", "bbb")));
TypeFreak t2 = db.tx(() -> db.typeFreaks().find(new TypeFreak.Id("tf", 1)));
assertEquals(t1, t2);
}
@Test
public void allTypesNull() {
TypeFreak t1 = db.tx(() -> {
TypeFreak t = new TypeFreak(new TypeFreak.Id("x", 1),
true,
(byte) 111,
(byte) 111,
(short) 22222,
1 << 30,
1L << 50,
1.5f,
0.5,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
singletonMap(1, new Embedded(new A("a"), new B("b"))),
singletonMap(1, new Embedded(new A("a"), new B("b"))),
singletonMap(1, new Embedded(new A("a"), new B("b"))),
singletonMap(1, new Embedded(new A("a"), new B("b"))),
null,
null,
null
);
db.typeFreaks().save(t);
return t;
});
TypeFreak t2 = db.tx(() -> db.typeFreaks().find(new TypeFreak.Id("x", 1)));
assertEquals(t1, t2);
}
@Test
public void allTypesWithNulls() {
TypeFreak t1 = db.tx(() -> {
TypeFreak t = new TypeFreak(new TypeFreak.Id("x", 1),
true,
(byte) 111,
(byte) 111,
(short) 22222,
1 << 30,
1L << 50,
1.5f,
0.5,
null,
null,
null,
null,
null,
1L >> 20,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Instant.now().truncatedTo(MILLIS), // we store ms for an java.time.Instant
null,
null,
null,
null,
null,
null,
null,
null,
"hi",
null
);
db.typeFreaks().save(t);
return t;
});
TypeFreak t2 = db.tx(() -> db.typeFreaks().find(new TypeFreak.Id("x", 1)));
assertEquals(t1, t2);
}
@Test
public void refs() {
Project p1 = new Project(new Project.Id("1"), "name1");
Project p2 = new Project(new Project.Id("2"), "name2");
Complex c1 = new Complex(new Complex.Id(1, 2L, "3", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(2, 2L, "3", Complex.Status.OK));
Referring r = new Referring(new Referring.Id("1"),
p1.getId(),
c1.getId(),
asList(p1.getId(), p2.getId()),
asList(c1.getId(), c2.getId())
);
db.tx(() -> {
db.projects().save(p1);
db.projects().save(p2);
db.complexes().save(c1);
db.complexes().save(c2);
db.referrings().save(r);
});
db.tx(() -> {
assertEquals(r, db.referrings().find(r.getId()));
assertEquals(p1, db.projects().find(r.getProject()));
assertEquals(c1, db.complexes().find(r.getComplex()));
assertEquals(asList(p1, p2), r.getProjects().stream().map(db.projects()::find).collect(toList()));
assertEquals(asList(c1, c2), r.getComplexes().stream().map(db.complexes()::find).collect(toList()));
});
}
@Test
public void severalWriteOperationsInTX() {
Complex.Id complexId = new Complex.Id(1, 1L, "c", Complex.Status.OK);
db.tx(() -> {
db.projects().save(new Project(new Project.Id("1"), "p11"));
db.projects().save(new Project(new Project.Id("2"), "p2"));
db.projects().save(new Project(new Project.Id("1"), "p12"));
db.complexes().save(new Complex(complexId));
db.projects().delete(new Project.Id("1"));
db.complexes().delete(complexId);
db.projects().delete(new Project.Id("2"));
});
db.tx(() -> {
assertNull(db.projects().find(new Project.Id("1")));
assertNull(db.projects().find(new Project.Id("2")));
assertNull(db.complexes().find(complexId));
});
}
@Test
public void insert() {
db.tx(() -> {
db.projects().insert(new Project(new Project.Id("unnamed-p1"), null));
db.projects().insert(new Project(new Project.Id("named-p2"), "P2"));
db.projects().insert(new Project(new Project.Id("named-p3"), "P3"));
});
Complex.Id complexId = new Complex.Id(1, 1L, "c", Complex.Status.OK);
Project p1 = new Project(new Project.Id("1"), "p1");
Complex complex = new Complex(complexId);
db.tx(() -> {
db.projects().insert(p1);
db.complexes().insert(complex);
});
db.tx(() -> {
assertEquals(p1, db.projects().find(new Project.Id("1")));
assertEquals(complex, db.complexes().find(complexId));
});
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> db.tx(() -> {
db.projects().insert(p1);
}));
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> db.tx(() -> {
db.complexes().insert(complex);
}));
}
@Test
public void alreadyExistsOnCommit() {
Project p1 = new Project(new Project.Id("1"), "p1");
Project p12 = new Project(new Project.Id("1"), "p2");
//this is a problem for one tx.
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> db.tx(() -> {
db.projects().insert(p1);
//this is a problem for one tx.
db.projects().insert(p12);
}));
db.tx(() -> db.projects().insert(p1));
AtomicBoolean executed = new AtomicBoolean(false);
//already exists only on commit
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> db.tx(() -> {
db.projects().insert(p1);
//already exists only on commit
executed.set(true);
}));
assertTrue(executed.get());
}
@Test
public void updateSimpleFieldById() {
db.tx(() -> db.projects().insert(new Project(new Project.Id("1"), "p1")));
db.tx(() -> db.projects().updateName(new Project.Id("1"), "NEW-P1-NAME"));
db.tx(() -> {
assertThat(db.projects().find(new Project.Id("1")).getName()).isEqualTo("NEW-P1-NAME");
});
}
@Test
public void updateComplexFieldByComplexId() {
TypeFreak tf = newTypeFreak(100500, "AAA", "BBB");
db.tx(() -> db.typeFreaks().insert(tf));
Embedded newEmbedded = new Embedded(new A("ZZZ"), new B("YYY"));
db.tx(() -> db.typeFreaks().updateEmbedded(tf.getId(), newEmbedded));
db.tx(() -> {
assertThat(db.typeFreaks().find(tf.getId()).getEmbedded()).isEqualTo(newEmbedded);
});
}
@Test
public void findByComplexIdUsingPredicates() {
db.tx(() -> db.typeFreaks().insert(
newTypeFreak(0, "AAA1", "bbb"),
newTypeFreak(1, "AAA2", "bbb")));
db.tx(() -> {
assertThat(db.typeFreaks().findByPredicateWithComplexId(new TypeFreak.Id("tf", 0)))
.isEqualTo(newTypeFreak(0, "AAA1", "bbb"));
});
}
@Test
public void findByManySimpleIdsUsingPredicates() {
Project projA = new Project(new Project.Id("A"), "aaa");
Project projB = new Project(new Project.Id("B"), "bbb");
Project proj0 = new Project(new Project.Id("0"), "000");
db.tx(() -> db.projects().insert(projA, projB, proj0));
db.tx(() -> {
assertThat(db.projects().findByPredicateWithManyIds(ImmutableSet.of(new Project.Id("A"), new Project.Id("B"))))
.containsOnly(projA, projB);
assertThat(db.projects().findByPredicateWithManyIdValues(ImmutableSet.of("A", "B")))
.containsOnly(projA, projB);
});
}
@Test
public void findViewById() {
TypeFreak tf1 = newTypeFreak(0, "AAA1", "bbb");
db.tx(() -> db.typeFreaks().insert(
tf1,
newTypeFreak(1, "AAA2", "bbb")));
db.tx(() -> {
TypeFreak.View found = db.typeFreaks().find(TypeFreak.View.class, tf1.getId());
assertThat(found).isEqualTo(new TypeFreak.View(tf1.getId(), tf1.getEmbedded()));
});
}
@Test
public void findViewTypeConversion() {
TypeFreak tf1 = newTypeFreak(0, "AAA1", "bbb");
db.tx(() -> db.typeFreaks().insert(
tf1,
newTypeFreak(1, "AAA2", "bbb")));
db.tx(() -> {
TypeFreak.StringView found = db.typeFreaks().find(TypeFreak.StringView.class, tf1.getId());
assertThat(found.getId()).isEqualTo(tf1.getId());
// ...sure looks like JSON to me:
assertThat(found.getStringEmbedded()).isNotNull();
assertThat(found.getStringEmbedded().trim()).startsWith("{");
assertThat(found.getStringEmbedded().trim()).endsWith("}");
});
}
@Test
public void findViewByIdRange() {
List toInsert = new ArrayList<>();
List expectedViews = new ArrayList<>();
for (int i = 0; i < 100; i++) {
TypeFreak tf = newTypeFreak(i, "AAA" + (i + 1), "bbb");
toInsert.add(tf);
if (i < 50) {
expectedViews.add(new TypeFreak.View(tf.getId(), tf.getEmbedded()));
}
}
Range findRange = Range.create(
expectedViews.get(0).getId(),
expectedViews.get(expectedViews.size() - 1).getId()
);
db.tx(() -> db.typeFreaks().insertAll(toInsert));
db.tx(() -> {
List found = db.typeFreaks().find(TypeFreak.View.class, findRange);
assertThat(found).containsExactlyInAnyOrderElementsOf(expectedViews);
});
}
@Test
public void findAllViews() {
List toInsert = new ArrayList<>();
List expectedViews = new ArrayList<>();
for (int i = 0; i < 100; i++) {
TypeFreak tf = newTypeFreak(i, "AAA" + (i + 1), "bbb");
toInsert.add(tf);
expectedViews.add(new TypeFreak.View(tf.getId(), tf.getEmbedded()));
}
db.tx(() -> db.typeFreaks().insertAll(toInsert));
db.tx(() -> {
List found = db.typeFreaks().findAll(TypeFreak.View.class);
assertThat(found).containsExactlyInAnyOrderElementsOf(expectedViews);
});
}
@Test
public void findViewByPredicate() {
List toInsert = new ArrayList<>();
List toFind = new ArrayList<>();
List embeddedAsToFind = new ArrayList<>();
for (int i = 0; i < 100; i++) {
String embeddedA = "AAA" + (i + 1);
TypeFreak tf = newTypeFreak(i, embeddedA, "bbb");
toInsert.add(tf);
toFind.add(new TypeFreak.View(tf.getId(), tf.getEmbedded()));
embeddedAsToFind.add(embeddedA);
}
db.tx(() -> db.typeFreaks().insertAll(toInsert));
db.tx(() -> {
List found = db.typeFreaks().findViewWithEmbeddedAIn(embeddedAsToFind);
assertThat(found).containsExactlyInAnyOrderElementsOf(toFind);
});
}
@Test
public void findRangeWithPrimitiveId() {
db.tx(() -> db.primitives().insert(
new Primitive(new Primitive.Id(1), 100500),
new Primitive(new Primitive.Id(42), 9000),
new Primitive(new Primitive.Id(-100500), 0)
));
db.tx(() -> {
assertThat(db.primitives().find(Range.create(new Primitive.Id(-5), new Primitive.Id(50))))
.containsOnly(
new Primitive(new Primitive.Id(1), 100500),
new Primitive(new Primitive.Id(42), 9000)
);
});
}
@Test
public void findAllResultsAreOrderedByIdAscending() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.FAIL));
Complex c3 = new Complex(new Complex.Id(999_999, 15L, "UUU", Complex.Status.OK));
Complex c4 = new Complex(new Complex.Id(999_999, 0L, "UUU", Complex.Status.OK));
Complex c5 = new Complex(new Complex.Id(999_000, 0L, "UUU", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3, c4, c5));
List found = db.tx(() -> db.complexes().findAll());
assertThat(found).containsExactly(c5, c4, c3, c2, c1);
}
@Test
public void findByRangeResultsAreOrderedByIdAscending() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.FAIL));
Complex c3 = new Complex(new Complex.Id(999_999, 15L, "UUU", Complex.Status.OK));
Complex c4 = new Complex(new Complex.Id(999_999, 0L, "UUU", Complex.Status.OK));
Complex c5 = new Complex(new Complex.Id(999_000, 0L, "UUU", Complex.Status.OK));
Complex c6 = new Complex(new Complex.Id(999_000, 0L, "AAA", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3, c4, c5, c6));
List found = db.tx(() -> db.complexes().find(
Range.create(
new Complex.Id(999_000, 0L, "AAA", null),
new Complex.Id(999_000, 0L, "UUU", null)
)));
assertThat(found).containsExactly(c6, c5);
}
@Test
public void findByPredicateResultsAreOrderedByIdAscending() {
db.tx(() -> db.projects().insert(
new Project(new Project.Id("named-p3"), "P3"),
new Project(new Project.Id("unnamed-p1"), null),
new Project(new Project.Id("named-p2"), "P2")
));
assertThat(db.tx(() -> db.projects().findNamed())).containsExactly(
new Project(new Project.Id("named-p2"), "P2"),
new Project(new Project.Id("named-p3"), "P3")
);
}
@Test
public void projections() {
db.tx(() -> {
db.table(Book.class).save(new Book(new Book.Id("1"), 1, "title1", List.of("author1")));
db.table(Book.class).save(new Book(new Book.Id("2"), 1, "title2", List.of("author2")));
db.table(Book.class).save(new Book(new Book.Id("3"), 1, null, List.of("author1", "author2")));
db.table(Book.class).save(new Book(new Book.Id("4"), 1, "title1", List.of()));
});
assertThat(db.tx(() -> db.table(Book.ByTitle.class).countAll()))
.isEqualTo(3L);
assertThat(db.tx(() -> db.table(Book.ByTitle.class).find(Range.create(new Book.ByTitle.Id("title1", null)))))
.hasSize(2);
assertThat(db.tx(() -> db.table(Book.ByTitle.class).find(Range.create(new Book.ByTitle.Id("title2", null)))))
.hasSize(1);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).countAll()))
.isEqualTo(4L);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).find(Range.create(new Book.ByAuthor.Id("author1", null)))))
.hasSize(2);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).find(Range.create(new Book.ByAuthor.Id("author2", null)))))
.hasSize(2);
db.tx(() -> {
db.table(Book.class).modifyIfPresent(new Book.Id("1"), b -> b.updateTitle("title2"));
db.table(Book.class).modifyIfPresent(new Book.Id("2"), b -> b.updateTitle(null));
db.table(Book.class).modifyIfPresent(new Book.Id("3"), b -> b.withAuthors(List.of("author2")));
db.table(Book.class).modifyIfPresent(new Book.Id("4"), b -> b.withAuthors(List.of("author1", "author2")));
});
assertThat(db.tx(() -> db.table(Book.ByTitle.class).countAll()))
.isEqualTo(2L);
assertThat(db.tx(() -> db.table(Book.ByTitle.class).find(Range.create(new Book.ByTitle.Id("title1", null)))))
.hasSize(1);
assertThat(db.tx(() -> db.table(Book.ByTitle.class).find(Range.create(new Book.ByTitle.Id("title2", null)))))
.hasSize(1);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).countAll()))
.isEqualTo(5L);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).find(Range.create(new Book.ByAuthor.Id("author1", null)))))
.hasSize(2);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).find(Range.create(new Book.ByAuthor.Id("author2", null)))))
.hasSize(3);
db.tx(() -> db.table(Book.class).findAll().forEach(b -> db.table(Book.class).delete(b.getId())));
assertThat(db.tx(() -> db.table(Book.ByTitle.class).countAll()))
.isEqualTo(0L);
assertThat(db.tx(() -> db.table(Book.ByAuthor.class).countAll()))
.isEqualTo(0L);
}
/**
* {@link #parallelTx(boolean, boolean, Consumer)} make two tx.
* In first - read from table (see consumers - findAll, findId, findRange)
* In second - insert Complex (1, 2L, "c", Complex.Status.OK)
* In first - again read same consumer
*/
@Test
public void rangeLock() {
parallelTx(true, true, Table::findAll); // lock on table
parallelTx(false, false, Table::findAll); // lock on table
parallelTx(true, true, t -> t.find(new Id(1, 2L, "c", Complex.Status.OK))); // lock on row
parallelTx(false, false, t -> t.find(new Id(1, 2L, "c", Complex.Status.OK))); // lock on row
parallelTx(false, true, t -> t.find(new Complex.Id(2, 4L, "l", Complex.Status.FAIL))); //not lock on another row
parallelTx(false, false, t -> t.find(new Complex.Id(2, 4L, "l", Complex.Status.FAIL))); //not lock on another row
parallelTx(true, true, t -> t.find(Range.create(new Complex.Id(1, 2L, null, null)))); //lock on range
parallelTx(false, false, t -> t.find(Range.create(new Complex.Id(1, 2L, null, null)))); //lock on range
parallelTx(false, true, t -> t.find(Range.create(new Complex.Id(2, 2L, null, null)))); //not lock on another range
parallelTx(false, false, t -> t.find(Range.create(new Complex.Id(2, 2L, null, null)))); //not lock on another range
}
private void parallelTx(boolean shouldThrown, boolean writeTx, Consumer> consumer) {
RepositoryTransaction tx = startTransaction();
if (writeTx) {
tx.table(Book.class).save(new Book(new Book.Id("1"), 1, "title1", List.of("author1")));
}
consumer.accept(tx.table(Complex.class));
db.tx(() -> db.complexes().insert(new Complex(new Complex.Id(1, 2L, "c", Complex.Status.OK))));
Runnable runnable = () -> {
try {
consumer.accept(tx.table(Complex.class));
} catch (Exception e) {
tx.rollback();
throw e;
}
tx.commit();
};
if (shouldThrown) {
assertThatExceptionOfType(OptimisticLockException.class).isThrownBy(runnable::run);
} else {
runnable.run();
}
db.tx(() -> db.complexes().deleteAll());
}
@Test
public void businessExceptionInTx() {
class BusinessException extends RuntimeException {
}
assertThatExceptionOfType(BusinessException.class).isThrownBy(() -> db.tx(() -> {
db.primitives().find(new Primitive.Id(25));
throw new BusinessException();
}));
}
@Test
public void readOnlyTransaction() {
assertThatExceptionOfType(IllegalTransactionIsolationLevelException.class)
.isThrownBy(() -> db.readOnly().run(() -> db.projects().save(new Project(new Project.Id("13"), "p13"))))
.withMessage("Mutable operations are not allowed for isolation level ONLINE_CONSISTENT_READ_ONLY");
}
@Test
public void ctorValidationFailure() {
EntityWithValidation goodValue = new EntityWithValidation(new EntityWithValidation.Id("hey"), 43L);
db.tx(() -> {
db.entitiesWithValidation().save(goodValue);
db.entitiesWithValidation().save(EntityWithValidation.BAD_VALUE);
});
assertThat(db.tx(() -> db.entitiesWithValidation().find(goodValue.getId())))
.isEqualTo(goodValue);
assertThatExceptionOfType(ConversionException.class)
.isThrownBy(() -> db.tx(() -> db.entitiesWithValidation().find(EntityWithValidation.BAD_VALUE.getId())));
}
@Test
public void viewCtorValidationFailure() {
EntityWithValidation goodValue = new EntityWithValidation(new EntityWithValidation.Id("hey"), 43L);
db.tx(() -> {
db.entitiesWithValidation().save(goodValue);
db.entitiesWithValidation().save(EntityWithValidation.BAD_VALUE_IN_VIEW);
});
assertThat(db.tx(() -> db.entitiesWithValidation().find(EntityWithValidation.OnlyVal.class, goodValue.getId())))
.isEqualTo(new EntityWithValidation.OnlyVal(goodValue.getValue()));
assertThatExceptionOfType(ConversionException.class)
.isThrownBy(() -> db.tx(() -> db.entitiesWithValidation()
.find(EntityWithValidation.OnlyVal.class, EntityWithValidation.BAD_VALUE_IN_VIEW.getId())));
}
@Test
public void complexIdEquals() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 42L, "ZZZ", Complex.Status.OK));
Complex c3 = new Complex(new Complex.Id(999_999, 76L, "ZZZ", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3));
assertThat(
db.tx(() -> db.complexes().query()
.where("id").eq(c1.getId())
.orderBy(ob -> ob.orderBy("id").ascending())
.find())
).containsExactly(c1);
}
@Test
public void complexIdNotEquals() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 42L, "ZZZ", Complex.Status.OK));
Complex c3 = new Complex(new Complex.Id(999_999, 76L, "ZZZ", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3));
assertThat(
db.tx(() -> db.complexes().query()
.where("id").neq(c1.getId())
.orderBy(ob -> ob.orderBy("id").ascending())
.find())
).containsExactly(c2, c3);
}
@Test
public void complexIdGreaterThan() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 42L, "ZZZ", Complex.Status.OK));
Complex c3 = new Complex(new Complex.Id(999_999, 76L, "ZZZ", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3));
assertThat(
db.tx(() -> db.complexes().query()
.where("id").gt(c1.getId())
.orderBy(ob -> ob.orderBy("id").ascending())
.find())
).containsExactly(c2, c3);
}
@Test
public void complexIdLessThan() {
Complex c1 = new Complex(new Complex.Id(999_999, 15L, "ZZZ", Complex.Status.OK));
Complex c2 = new Complex(new Complex.Id(999_999, 42L, "ZZZ", Complex.Status.OK));
Complex c3 = new Complex(new Complex.Id(999_999, 76L, "ZZZ", Complex.Status.OK));
db.tx(() -> db.complexes().insert(c1, c2, c3));
assertThat(
db.tx(() -> db.complexes().query()
.where("id").lt(c3.getId())
.orderBy(ob -> ob.orderBy("id").descending())
.find())
).containsExactly(c2, c1);
}
@Test
public void byteIdLessThan() {
BytePkEntity e0 = BytePkEntity.valueOf();
BytePkEntity e1 = BytePkEntity.valueOf(1, 2, 3);
BytePkEntity e2 = BytePkEntity.valueOf(1, 2, 4);
BytePkEntity e3 = BytePkEntity.valueOf(1, 3, 255);
db.tx(() -> db.bytePkEntities().insert(e0, e1, e2, e3));
assertThat(db.tx(() -> db.bytePkEntities().query()
.where("id").lt(e3.getId())
.orderBy(ob -> ob.orderBy("id").descending())
.find()
)).containsExactly(e2, e1, e0);
}
@Test
public void byteIdGreaterThan() {
BytePkEntity e0 = BytePkEntity.valueOf();
BytePkEntity e1 = BytePkEntity.valueOf(1, 2, 3);
BytePkEntity e2 = BytePkEntity.valueOf(1, 2, 4);
BytePkEntity e3 = BytePkEntity.valueOf(1, 3, 255);
db.tx(() -> db.bytePkEntities().insert(e0, e1, e2, e3));
assertThat(db.tx(() -> db.bytePkEntities().query()
.where("id").gt(e1.getId())
.orderBy(ob -> ob.orderBy("id").ascending())
.find()
)).containsExactly(e2, e3);
}
@Test
public void byteIdEmpty() {
BytePkEntity e0 = BytePkEntity.valueOf();
db.tx(() -> db.bytePkEntities().insert(e0));
assertThat(db.tx(() -> db.bytePkEntities().find(e0.getId()))).isEqualTo(e0);
}
@Test
public void complexIdLessThanWithEmbeddedId() {
Supabubble sa = new Supabubble(new Supabubble.Id(new Project.Id("naher"), "bubble-A"));
Supabubble sb = new Supabubble(new Supabubble.Id(new Project.Id("naher"), "bubble-B"));
Supabubble sc = new Supabubble(new Supabubble.Id(new Project.Id("naher"), "bubble-C"));
db.tx(() -> db.supabubbles().insert(sa, sb, sc));
assertThat(
db.tx(() -> db.supabubbles().query()
.where("id").lt(sc.getId())
.orderBy(ob -> ob.orderBy("id").descending())
.find())
).containsExactly(sb, sa);
}
@Test
public void checkCanMergeWorkProperly() {
db.tx(() -> {
Project p1 = new Project(new Project.Id("1"), "first");
Project p2 = new Project(new Project.Id("2"), "second");
db.projects().insert(p1);
db.projects().delete(p2.getId());
db.projects().insert(p2);
});
}
@Test
public void doMultipleSaveInOneTx() {
Project p1 = new Project(new Project.Id("1"), "first");
Project p2 = new Project(new Project.Id("2"), "second");
db.tx(() -> {
db.projects().save(p1);
db.projects().save(p2);
});
assertThat(db.tx(() -> db.projects().find(p1.getId()))).isEqualTo(p1);
assertThat(db.tx(() -> db.projects().find(p2.getId()))).isEqualTo(p2);
}
@Test
public void deleteInsertInOneTx() {
// merged to upsert, integration test, that's merge correct
Project p1 = new Project(new Project.Id("1"), "project1");
Project p2 = new Project(new Project.Id("2"), "project2");
db.tx(() -> {
db.projects().delete(p1.getId());
db.projects().insert(p1);
db.projects().delete(p2.getId());
db.projects().insert(p2);
});
assertThat(db.tx(() -> db.projects().find(p1.getId()))).isEqualTo(p1);
assertThat(db.tx(() -> db.projects().find(p2.getId()))).isEqualTo(p2);
}
@Test
public void multistatementReadonlyTransaction() {
Project p1 = new Project(new Project.Id("1"), "first");
Project p2 = new Project(new Project.Id("2"), "second");
db.tx(() -> {
db.projects().save(p1);
db.projects().save(p2);
});
RepositoryTransaction tx = repository.startTransaction(TxOptions.create(IsolationLevel.STALE_CONSISTENT_READ_ONLY));
assertThat(tx.table(Project.class).find(p1.getId())).isNotNull();
assertThat(tx.table(Project.class).find(p2.getId())).isNotNull();
}
@Test
public void noOptimisticLockOnScan() {
RepositoryTransaction tx0 = repository.startTransaction(TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE));
tx0.table(Project.class).save(new Project(new Project.Id("1"), "p1"));
tx0.table(Project.class).save(new Project(new Project.Id("2"), "p2"));
tx0.commit();
TxOptions txOptions = TxOptions.create(IsolationLevel.STALE_CONSISTENT_READ_ONLY)
.withScanOptions(TxOptions.ScanOptions.DEFAULT);
RepositoryTransaction tx1 = repository.startTransaction(txOptions);
tx1.table(Project.class).find(new Project.Id("1"));
{
RepositoryTransaction tx2 = repository.startTransaction(TxOptions.create(IsolationLevel.SERIALIZABLE_READ_WRITE));
tx2.table(Project.class).save(new Project(new Project.Id("1"), "p1-1"));
tx2.commit();
}
//не prepare запросы не валятся при обращении
try {
tx1.table(Project.class).find(new Project.Id("1"));
} catch (Exception e) {
tx1.rollback();
throw e;
}
//prepare запросы не валятся на комите
tx1.commit();
}
@Test
public void findEntityAndViewWithTheSameKey() {
TypeFreak tf1 = newTypeFreak(0, "AAA1", "bbb");
db.tx(() -> db.typeFreaks().insert(tf1));
// find view by id and than find entity by id
db.tx(() -> {
TypeFreak.View foundView = db.typeFreaks().find(TypeFreak.View.class, tf1.getId());
assertThat(foundView).isEqualTo(new TypeFreak.View(tf1.getId(), tf1.getEmbedded()));
TypeFreak foundTF = db.typeFreaks().find(tf1.getId());
assertThat(foundTF).isEqualTo(tf1);
});
// find entity by id and than find view by id
db.tx(() -> {
TypeFreak foundTF = db.typeFreaks().find(tf1.getId());
assertThat(foundTF).isEqualTo(tf1);
TypeFreak.View foundView = db.typeFreaks().find(TypeFreak.View.class, tf1.getId());
assertThat(foundView).isEqualTo(new TypeFreak.View(tf1.getId(), tf1.getEmbedded()));
});
}
@Test
public void scanUpdateFails() {
Assertions.assertThatExceptionOfType(IllegalTransactionScanException.class)
.isThrownBy(() -> db.scan().run(() -> {
db.projects().save(new Project(new Project.Id("1"), "p1"));
}));
}
@Test
public void scanNotTruncated() {
int maxPageSizeBiggerThatReal = 11_000;
db.tx(() -> IntStream.range(0, maxPageSizeBiggerThatReal).forEach(
i -> db.projects().save(new Project(new Project.Id("id_" + i), "name"))
));
List result = db.scan().withMaxSize(maxPageSizeBiggerThatReal).run(() -> db.projects().findAll());
assertEquals(maxPageSizeBiggerThatReal, result.size());
}
@Test
public void scanFind() {
Project p1 = new Project(new Project.Id("1"), "p1");
db.tx(() -> {
db.projects().save(p1);
});
Project result = db.scan().run(() -> db.projects().find(p1.getId()));
assertEquals(p1, result);
}
@Test
public void scanStreamAll() {
int size = 10;
db.tx(() -> IntStream.range(0, size).forEach(
i -> db.projects().save(new Project(new Project.Id("id_" + i), "name"))
));
List result = db.scan().run(() -> db.projects().streamAll(1).collect(toList()));
assertEquals(size, result.size());
}
@Test
public void businessExceptionInScanTx() {
class BusinessException extends RuntimeException {
}
assertThatExceptionOfType(BusinessException.class).isThrownBy(() -> db.tx(() -> {
db.primitives().find(new Primitive.Id(25));
throw new BusinessException();
}));
}
@Test
public void throwConversionExceptionOnDeserializationProblem() {
NonDeserializableEntity nonDeserializableEntity = new NonDeserializableEntity(
new NonDeserializableEntity.Id("ru-vladimirsky-central-001"),
new NonDeserializableObject()
);
db.tx(() -> db.table(NonDeserializableEntity.class).insert(nonDeserializableEntity));
assertThatExceptionOfType(ConversionException.class)
.isThrownBy(() -> db.tx(() -> db.table(NonDeserializableEntity.class).find(nonDeserializableEntity.getId())));
}
@Test
public void throwConversionExceptionOnDeserializationReadTableProblem() {
NonDeserializableEntity nonDeserializableEntity = new NonDeserializableEntity(
new NonDeserializableEntity.Id("ru-vladimirsky-central-001"),
new NonDeserializableObject()
);
db.tx(() -> db.table(NonDeserializableEntity.class).insert(nonDeserializableEntity));
assertThatExceptionOfType(ConversionException.class).isThrownBy(() ->
db.readOnly().run(() -> db.table(NonDeserializableEntity.class).readTable(defaultReadTableParamsNonLegacy()).collect(toList()))
);
}
@Test
public void resolveOnReadTableStream() {
db.tx(() -> {
db.projects().save(new Project(new Project.Id("1"), "p1"));
db.referrings().save(new Referring(new Referring.Id("1"), new Project.Id("1"), null, null, null));
db.projects().save(new Project(new Project.Id("2"), "p2"));
db.referrings().save(new Referring(new Referring.Id("2"), new Project.Id("2"), null, null, null));
});
db.readOnly().run(() ->
db.referrings().readTable(defaultReadTableParamsNonLegacy())
.forEach(r -> db.projects().find(r.getProject()))
);
}
@Test
public void customMarshaling() {
WithUnflattenableField entity = new WithUnflattenableField(
new WithUnflattenableField.Id("id42"),
new WithUnflattenableField.Unflattenable("Hello, world!", 100_500)
);
db.tx(() -> db.table(WithUnflattenableField.class).insert(entity));
db.tx(() -> {
assertThat(db.table(WithUnflattenableField.class).find(entity.getId())).isEqualTo(entity);
});
}
@Test
public void readFromCache() {
Complex.Id id = new Complex.Id(1, 2L, "c", Complex.Status.OK);
db.tx(() -> {
db.complexes().insert(new Complex(id));
});
db.tx(() -> {
Complex first = db.complexes().find(id);
Complex second = db.complexes().find(id);
// Check, that there are the same objects, it's mean they was returned from cache
assertThat(second).isSameAs(first);
});
}
@Test
public void findRangeAndPutInCache() {
db.tx(this::makeComplexes);
db.tx(() -> {
List rangeResults = db.complexes().find(Range.create(new Complex.Id(0, 0L, null, null)));
assertEquals(6, rangeResults.size());
// Check, that there are the same objects, it's mean they was returned from cache
for (Complex c : rangeResults) {
Complex newFound = db.complexes().find(c.getId());
assertThat(newFound).isSameAs(c);
}
});
}
@Test
public void findAllAndPutInCache() {
db.tx(this::makeComplexes);
db.tx(() -> {
List rangeResults = db.complexes().findAll();
assertEquals(54, rangeResults.size());
// Check, that there are the same objects, it's mean they was returned from cache
for (Complex c : rangeResults) {
Complex newFound = db.complexes().find(c.getId());
assertThat(newFound).isSameAs(c);
}
});
}
@Test
public void readAndFailOnInconsistentDataSucceedOnRetry() {
// 3 entities are possible, one in each of 3 different tables. All are named after 3 famous sith of "Star Wars".
final Primitive.Id sidiousId = new Primitive.Id(1L);
final Complex.Id tyranusId = new Complex.Id(2, 0L, "Tyranus", Complex.Status.OK);
final Project.Id vaderId = new Project.Id("Vader");
// But 'Always two there are', we consistently keep exactly 2 entities altogether:
Consumer createFirstTwo = (tx) -> {
tx.table(Primitive.class).insert(new Primitive(sidiousId, 10));
tx.table(Complex.class).insert(new Complex(tyranusId));
};
// Do initial transaction:
runInTx(createFirstTwo);
// Prepare altering transaction as a hook that doesn't do anything in the second attempt.
Runnable concurrentChanger = makeOneShotRunnable(() -> db.separate().tx(() -> {
db.complexes().delete(tyranusId);
db.projects().insert(new Project(vaderId, "abc"));
}));
List txAttempts = new ArrayList<>();
List> twoElements = db.tx(() -> {
txAttempts.add(null);
db.table(Book.class).save(new Book(new Book.Id("1"), 1, "title1", List.of("author1")));
List> result = new ArrayList<>();
result.add(db.complexes().find(tyranusId));
// This should invalidate the current tx on the first attempt.
concurrentChanger.run();
result.add(db.primitives().find(sidiousId));
result.add(db.projects().find(vaderId));
result.removeIf(Objects::isNull);
if (result.size() > 2) {
throw new RuntimeException("We are seeing inconsistent data, 'always two there are' rule is broken, we found " + result.size());
}
return result;
});
assertThat(txAttempts).hasSize(2);
assertThat(twoElements).hasSize(2);
}
@Test
public void customValuedIds() {
Map inserted = new HashMap<>();
for (int i = 0; i < 100; i++) {
var snap = new UpdateFeedEntry(
UpdateFeedEntry.Id.generate("insert"),
Instant.now(),
"payload-" + i,
Math.random() < 0.5 ? UpdateFeedEntry.Status.ACTIVE : UpdateFeedEntry.Status.INACTIVE
);
db.tx(() -> db.updateFeedEntries().insert(snap));
inserted.put(snap.getId(), snap);
}
assertThat(db.tx(() -> db.updateFeedEntries().find(inserted.keySet())))
.containsExactlyInAnyOrderElementsOf(inserted.values());
assertThat(db.tx(() -> db.updateFeedEntries().find(inserted.keySet())))
.containsExactlyInAnyOrderElementsOf(inserted.values());
assertThat(db.tx(() -> db.updateFeedEntries().list(ListRequest.builder(UpdateFeedEntry.class)
.filter(fb -> fb.where("id").in(inserted.keySet()))
.build())))
.containsExactlyInAnyOrderElementsOf(inserted.values());
assertThat(db.tx(() -> db.updateFeedEntries().list(ListRequest.builder(UpdateFeedEntry.class)
.filter(fb -> fb.where("id").in(inserted.keySet().stream().map(i -> i.toString()).collect(toSet())))
.build())))
.containsExactlyInAnyOrderElementsOf(inserted.values());
for (var e : inserted.entrySet()) {
assertThat(db.tx(() -> db.updateFeedEntries().query()
.filter(fb -> fb.where("id").eq(e.getKey()))
.findOne())).isEqualTo(e.getValue());
assertThat(db.tx(() -> db.updateFeedEntries().query()
.filter(fb -> fb.where("id").eq(e.getKey().toString()))
.findOne())).isEqualTo(e.getValue());
}
}
@Test
@SneakyThrows
public void customValueType() {
var app1 = new NetworkAppliance(
new NetworkAppliance.Id("app1"),
new NetworkAppliance.Ipv6Address("2e:a0::1"),
new NetworkAppliance.SixtyFourBitString(Long.parseUnsignedLong("cafecafecafecafe", 16))
);
db.tx(() -> db.networkAppliances().insert(app1));
assertThat(db.tx(() -> db.networkAppliances().find(app1.id()))).isEqualTo(app1);
}
@Test
public void customValueTypeInFilter() {
var ve = new VersionedEntity(new VersionedEntity.Id("heyhey", new Version(100L)), new Version(100_500L));
db.tx(() -> db.versionedEntities().insert(ve));
assertThat(db.tx(() -> db.versionedEntities().find(ve.id()))).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedEntities().query()
.where("id.version").eq(ve.id().version())
.and("version2").eq(ve.version2())
.findOne()
)).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedEntities().query()
.where("id.version").eq(100L)
.and("version2").eq(100_500L)
.findOne()
)).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedEntities().query()
.where("id.version").eq(100L)
.and("version2").eq(null)
.findOne()
)).isNull();
}
@Test
public void customValueTypeInFilterByAlias() {
UUID testPrefferedUUID = UUID.randomUUID();
var ve = new VersionedAliasedEntity(new VersionedAliasedEntity.Id("heyhey", new Version(100L), testPrefferedUUID, new Sha256("100")), new Version(100_500L), testPrefferedUUID, new UniqueEntity.Id(testPrefferedUUID));
db.tx(() -> db.versionedAliasedEntities().insert(ve));
assertThat(db.tx(() -> db.versionedAliasedEntities().find(ve.id()))).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedAliasedEntities().query()
.where("id.version").eq(ve.id().version())
.and("version2").eq(ve.version2())
.findOne()
)).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedAliasedEntities().query()
.where("id.version").eq(100L)
.and("version2").eq(100_500L)
.findOne()
)).isEqualTo(ve);
assertThat(db.tx(() -> db.versionedAliasedEntities().query()
.where("id.version").eq(100L)
.and("version2").eq(null)
.findOne()
)).isNull();
assertThat(db.tx(() -> db.versionedAliasedEntities().query()
.where("uniqueId").eq(ve.uniqueId())
.findOne()
)).isEqualTo(ve);
}
@Test
public void detachedEntity() {
var theEntity = new DetachedEntity(new DetachedEntityId("some-id"));
db.tx(() -> db.detachedEntities().save(theEntity));
assertThat(db.tx(() -> db.detachedEntities().find(theEntity.id()))).isEqualTo(theEntity);
}
protected void runInTx(Consumer action) {
// We do not retry transactions, because we do not expect conflicts in our test scenarios.
RepositoryTransaction transaction = startTransaction();
try {
action.accept(transaction);
} catch (Throwable t) {
transaction.rollback();
throw t;
}
transaction.commit();
}
private void testList(FilterExpression filterExpression, Project... expectedProjects) {
ListRequest request = ListRequest.builder(Project.class)
.filter(filterExpression)
.build();
ListResult result = db.readOnly().run(() -> db.projects().list(request));
Set expected = new HashSet<>(Arrays.asList(expectedProjects));
assertEquals(expected, new HashSet<>(result.getEntries()));
}
protected static Runnable makeOneShotRunnable(Runnable cmd) {
return new Runnable() {
private boolean doneOnce = false;
@Override
public void run() {
if (doneOnce) {
return;
}
doneOnce = true;
cmd.run();
}
};
}
}