package org.javersion.store.jdbc;
import static java.util.Arrays.asList;
import static java.util.UUID.randomUUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.javersion.store.jdbc.VersionStatus.ACTIVE;
import static org.javersion.store.jdbc.VersionStatus.REDUNDANT;
import static org.javersion.store.jdbc.VersionStatus.SQUASHED;
import static org.javersion.store.sql.QEntity.entity;
import static org.javersion.store.sql.QEntityVersion.entityVersion;
import static org.javersion.store.sql.QEntityVersionParent.entityVersionParent;
import static org.javersion.store.sql.QEntityVersionProperty.entityVersionProperty;
import java.util.List;
import javax.annotation.Resource;
import org.javersion.core.VersionNotFoundException;
import org.javersion.object.ObjectVersion;
import org.javersion.object.ObjectVersionGraph;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.querydsl.sql.SQLQueryFactory;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = PersistenceTestConfiguration.class)
public class EntityVersionStoreJdbcTest extends AbstractVersionStoreTest {
private final String
docId1 = randomId(),
docId2 = randomId();
@Resource
CustomEntityVersionStore entityStore;
@Resource
SQLQueryFactory queryFactory;
@Test
public void should_return_empty_graph_if_not_found() {
assertThat(entityStore.getFullGraph(randomId()).isEmpty()).isTrue();
}
@Test(expected = IllegalStateException.class)
public void must_lock_entity_before_update() {
transactionTemplate.execute(status -> {
EntityUpdateBatch<String, String, JEntityVersion<String>> update = entityStore.updateBatch(randomId());
update.addVersion(randomId(), ObjectVersionGraph.init(ObjectVersion.<String>builder().build()).getTip());
update.execute();
return null;
});
}
@Test(expected = VersionNotFoundException.class)
public void throws_exception_if_since_revision_is_not_found() {
entityStore.fetchUpdates(docId2, rev1);
}
@Test
public void save_read_and_update_flow() {
create_first_two_versions_of_doc1();
cannot_bulk_load_before_publish();
create_doc2_and_update_doc1();
fetch_updates_of_doc1();
fetch_updates_of_doc2();
bulk_load_after_publish();
prune_doc1();
}
@Test
public void save_and_load_version_comment() {
final String comment = "Comment metadata";
final String docId = randomId();
transactionTemplate.execute(status -> {
EntityUpdateBatch<String, String, JEntityVersion<String>> update = entityStore.updateBatch(docId);
ObjectVersion<String> version = ObjectVersion.<String>builder()
.meta(comment)
.build();
update.addVersion(docId, ObjectVersionGraph.init(version).getTip());
update.execute();
return null;
});
ObjectVersionGraph<String> graph = entityStore.getFullGraph(docId);
assertThat(graph.getTip().getMeta()).isEqualTo(comment);
}
@Override
protected void verifyRedundantRelations() {
// Redundant parents of inactive versions are removed
assertThat(queryFactory
.from(entityVersion)
.innerJoin(entityVersion._entityVersionParentParentRevisionFk, entityVersionParent)
.where(entityVersion.status.eq(SQUASHED), entityVersionParent.status.eq(REDUNDANT))
.fetchCount())
.isEqualTo(0);
// Verify that inverse is true: there exists redundant parents on ACTIVE versions
assertThat(queryFactory
.from(entityVersion)
.innerJoin(entityVersion._entityVersionParentParentRevisionFk, entityVersionParent)
.where(entityVersion.status.eq(ACTIVE), entityVersionParent.status.eq(REDUNDANT))
.fetchCount())
.isGreaterThan(0);
// Redundant properties of inactive versions are removed
assertThat(queryFactory
.from(entityVersion)
.innerJoin(entityVersion._entityVersionPropertyRevisionFk, entityVersionProperty)
.where(entityVersion.status.eq(SQUASHED), entityVersionProperty.status.eq(REDUNDANT))
.fetchCount())
.isEqualTo(0);
// Verify that inverse is true: there exists redundant properties on ACTIVE versions
assertThat(queryFactory
.from(entityVersion)
.innerJoin(entityVersion._entityVersionPropertyRevisionFk, entityVersionProperty)
.where(entityVersion.status.eq(ACTIVE), entityVersionProperty.status.eq(REDUNDANT))
.fetchCount())
.isGreaterThan(0);
}
private void create_first_two_versions_of_doc1() {
transactionTemplate.execute(status -> {
EntityUpdateBatch<String, String, ?> update = entityStore.updateBatch(docId1);
assertThat(update.contains(docId1)).isTrue();
assertThat(update.contains(randomId())).isFalse();
assertThat(update.isCreate(docId1)).isTrue();
assertThat(update.isUpdate(docId1)).isFalse();
ObjectVersion<String> v1 = ObjectVersion.<String>builder(rev1)
.changeset(mapOf(
"id", docId1,
"name", "name of " + docId1))
.build();
ObjectVersionGraph<String> graph = ObjectVersionGraph.init(v1);
update.addVersion(docId1, graph.getTip());
assertThat(update.isCreate(docId1)).isFalse();
assertThat(update.isUpdate(docId1)).isTrue();
ObjectVersion<String> v2 = ObjectVersion.<String>builder(rev2)
.parents(rev1)
.changeset(mapOf(
"name", "Fixed name"))
.build();
graph = graph.commit(v2);
update.addVersion(docId1, graph.getTip());
update.execute();
return null;
});
ObjectVersionGraph<String> graph = entityStore.getFullGraph(docId1);
assertThat(graph.getTip().getProperties()).isEqualTo(mapOf(
"id", docId1,
"name", "Fixed name"
));
String persistedName = queryFactory
.select(entity.name)
.from(entity)
.where(entity.id.eq(docId1))
.fetchOne();
assertThat(persistedName).isEqualTo("Fixed name");
}
private void cannot_bulk_load_before_publish() {
GraphResults<String, String> graphs = entityStore.getGraphs(asList(docId1, docId2));
assertThat(graphs.isEmpty()).isTrue();
assertThat(graphs.size()).isEqualTo(0);
}
private void create_doc2_and_update_doc1() {
transactionTemplate.execute(status -> {
EntityUpdateBatch<String, String, JEntityVersion<String>> update = entityStore.updateBatch(asList(docId1, docId2));
ObjectVersionGraph<String> graph = entityStore.getFullGraph(docId1);
assertThat(graph.isEmpty()).isFalse();
// Create doc2
ObjectVersion<String> v3 = ObjectVersion.<String>builder(rev3).changeset(mapOf("name", "doc2")).build();
update.addVersion(docId2, ObjectVersionGraph.init(v3).getTip());
// Update doc1
ObjectVersion<String> v4 = ObjectVersion.<String>builder(rev4).parents(rev2).changeset(mapOf("name", "doc1")).build();
update.addVersion(docId1, graph.commit(v4).getTip());
update.execute();
return null;
});
}
private void fetch_updates_of_doc1() {
List<ObjectVersion<String>> updates = entityStore.fetchUpdates(docId1, rev1);
assertThat(updates).hasSize(2);
assertThat(updates.get(0).revision).isEqualTo(rev2);
assertThat(updates.get(1).revision).isEqualTo(rev4);
}
private void fetch_updates_of_doc2() {
List<ObjectVersion<String>> updates = entityStore.fetchUpdates(docId2, rev3);
assertThat(updates).isEmpty();
}
private void bulk_load_after_publish() {
entityStore.publish();
GraphResults<String, String> graphs = entityStore.getGraphs(asList(docId1, docId2));
assertThat(graphs.containsKey(docId1)).isTrue();
assertThat(graphs.containsKey(docId2)).isTrue();
assertThat(graphs.getVersionGraph(docId1).getTip().getProperties()).isEqualTo(mapOf(
"id", docId1,
"name", "doc1"
));
assertThat(graphs.getVersionGraph(docId2).getTip().getProperties()).isEqualTo(mapOf(
"name", "doc2"
));
String persistedName = queryFactory
.select(entity.name)
.from(entity)
.where(entity.id.eq(docId1))
.fetchOne();
assertThat(persistedName).isEqualTo("doc1");
persistedName = queryFactory
.select(entity.name)
.from(entity)
.where(entity.id.eq(docId2))
.fetchOne();
assertThat(persistedName).isEqualTo("doc2");
}
private void prune_doc1() {
entityStore.prune(docId1, graph -> v -> v.revision.equals(rev4));
ObjectVersionGraph<String> graph = entityStore.getFullGraph(docId1);
assertThat(graph.size()).isEqualTo(1);
}
private String randomId() {
return randomUUID().toString();
}
@Override
protected AbstractVersionStoreJdbc<String, String, ?, ?, ?> getStore() {
return entityStore;
}
@Override
@SuppressWarnings("unchecked")
protected AbstractVersionStoreJdbc<String, String, ?, ?, ?> newStore(StoreOptions options) {
return new CustomEntityVersionStore((EntityStoreOptions<String, String, JEntityVersion<String>>) options);
}
}