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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package uk.co.compendiumdev.thingifier.core.repository;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;

public final class ThingStoreWriteException extends RuntimeException {

public enum Reason {
MAX_INSTANCE_LIMIT_REACHED,
MAX_INSTANCE_LIMIT_WOULD_BE_EXCEEDED,
DUPLICATE_PRIMARY_KEY,
MISSING_PRIMARY_KEY,
WRONG_ENTITY_TYPE
}

private final Reason reason;
private final String entityName;
private final Map<String, String> details;

private ThingStoreWriteException(
final Reason reason,
final String entityName,
final String message,
final Map<String, String> details) {
super(message);
this.reason = reason;
this.entityName = entityName;
this.details = Collections.unmodifiableMap(new HashMap<>(details));
}

public static ThingStoreWriteException maxInstanceLimitReached(final EntityDefinition entity) {
return new ThingStoreWriteException(
Reason.MAX_INSTANCE_LIMIT_REACHED,
entity.getName(),
String.format(
"ERROR: Cannot add instance, maximum limit of %d reached",
entity.getMaxInstanceLimit()),
Map.of("maxInstances", String.valueOf(entity.getMaxInstanceLimit())));
}

public static ThingStoreWriteException maxInstanceLimitWouldBeExceeded(
final EntityDefinition entity) {
return new ThingStoreWriteException(
Reason.MAX_INSTANCE_LIMIT_WOULD_BE_EXCEEDED,
entity.getName(),
String.format(
"ERROR: Cannot add instances, would exceed maximum limit of %d",
entity.getMaxInstanceLimit()),
Map.of("maxInstances", String.valueOf(entity.getMaxInstanceLimit())));
}

public static ThingStoreWriteException missingPrimaryKey(
final EntityDefinition entity, final String fieldName) {
return new ThingStoreWriteException(
Reason.MISSING_PRIMARY_KEY,
entity.getName(),
String.format(
"ERROR: Cannot add instance, primary key field %s not set", fieldName),
Map.of("fieldName", fieldName));
}

public static ThingStoreWriteException duplicatePrimaryKey(
final EntityDefinition entity, final String primaryKeyValue) {
return new ThingStoreWriteException(
Reason.DUPLICATE_PRIMARY_KEY,
entity.getName(),
"ERROR: Cannot add instance, another instance with primary key value exists: "
+ primaryKeyValue,
Map.of("primaryKeyValue", primaryKeyValue));
}

public static ThingStoreWriteException wrongEntityType(
final EntityDefinition expectedEntity, final EntityDefinition actualEntity) {
return new ThingStoreWriteException(
Reason.WRONG_ENTITY_TYPE,
expectedEntity.getName(),
String.format(
"ERROR: Tried to add a %s instance to the %s",
actualEntity.getName(), expectedEntity.getName()),
Map.of("actualEntityName", actualEntity.getName()));
}

public Reason reason() {
return reason;
}

public String entityName() {
return entityName;
}

public Map<String, String> details() {
return details;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
import uk.co.compendiumdev.thingifier.core.reporting.ValidationReport;
import uk.co.compendiumdev.thingifier.core.repository.MutableEntityInstance;
import uk.co.compendiumdev.thingifier.core.repository.ThingStoreWriteException;

final class InMemoryEntityInstanceCollection {

Expand Down Expand Up @@ -55,10 +56,7 @@ InMemoryEntityInstanceCollection addInstances(List<EntityInstance> addInstances)

if (definition.hasMaxInstanceLimit()
&& ((instances.size() + addInstances.size()) > definition.getMaxInstanceLimit())) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instances, would exceed maximum limit of %d",
definition.getMaxInstanceLimit()));
throw ThingStoreWriteException.maxInstanceLimitWouldBeExceeded(definition);
}

for (EntityInstance instance : addInstances) {
Expand All @@ -77,18 +75,12 @@ EntityInstance prepareInstanceForInsert(final MutableEntityInstance mutableInsta
ensureCountersInitialized();

if (mutableInstance.getEntity() != definition) {
throw new RuntimeException(
String.format(
"ERROR: Tried to add a %s instance to the %s",
mutableInstance.getEntity().getName(), definition.getName()));
throw ThingStoreWriteException.wrongEntityType(definition, mutableInstance.getEntity());
}

if (definition.hasMaxInstanceLimit()
&& instances.size() >= definition.getMaxInstanceLimit()) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, maximum limit of %d reached",
definition.getMaxInstanceLimit()));
throw ThingStoreWriteException.maxInstanceLimitReached(definition);
}

// if there are any AUTO_GUIDs or AUTO-INCREMENTs not set in the instance, then set them now
Expand All @@ -114,10 +106,8 @@ EntityInstance prepareInstanceForInsert(final MutableEntityInstance mutableInsta
// check value of primary key exists and is unique
Field primaryField = definition.getPrimaryKeyField();
if (!mutableInstance.hasInstantiatedFieldNamed(primaryField.getName())) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, primary key field %s not set",
primaryField.getName()));
throw ThingStoreWriteException.missingPrimaryKey(
definition, primaryField.getName());
}
}

Expand All @@ -127,10 +117,8 @@ EntityInstance prepareInstanceForInsert(final MutableEntityInstance mutableInsta

for (EntityInstance existingInstance : instances.values()) {
if (existingInstance.getPrimaryKeyValue().equals(instance.getPrimaryKeyValue())) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, another instance with primary key value exists: %s",
existingInstance.getPrimaryKeyValue()));
throw ThingStoreWriteException.duplicatePrimaryKey(
definition, existingInstance.getPrimaryKeyValue());
}
}
}
Expand All @@ -143,18 +131,12 @@ EntityInstance addInstance(EntityInstance instance) {
ensureCountersInitialized();

if (instance.getEntity() != definition) {
throw new RuntimeException(
String.format(
"ERROR: Tried to add a %s instance to the %s",
instance.getEntity().getName(), definition.getName()));
throw ThingStoreWriteException.wrongEntityType(definition, instance.getEntity());
}

if (definition.hasMaxInstanceLimit()
&& instances.size() >= definition.getMaxInstanceLimit()) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, maximum limit of %d reached",
definition.getMaxInstanceLimit()));
throw ThingStoreWriteException.maxInstanceLimitReached(definition);
}

instances.put(instance.getInternalId(), instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import uk.co.compendiumdev.thingifier.core.repository.RepositoryAdministration;
import uk.co.compendiumdev.thingifier.core.repository.ThingStore;
import uk.co.compendiumdev.thingifier.core.repository.ThingStoreTransaction;
import uk.co.compendiumdev.thingifier.core.repository.ThingStoreWriteException;
import uk.co.compendiumdev.thingifier.core.repository.relationship.RelationshipEndpoint;
import uk.co.compendiumdev.thingifier.core.repository.relationship.RelationshipRow;
import uk.co.compendiumdev.thingifier.core.repository.relationship.RelationshipRules;
Expand Down Expand Up @@ -563,10 +564,7 @@ private void prepareInstanceForInsert(final MutableEntityInstance instance) {

if (entity.hasMaxInstanceLimit()
&& countInstances(entity) >= entity.getMaxInstanceLimit()) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, maximum limit of %d reached",
entity.getMaxInstanceLimit()));
throw ThingStoreWriteException.maxInstanceLimitReached(entity);
}

List<String> explicitAutoIncrementFields = new ArrayList<>();
Expand All @@ -588,18 +586,14 @@ && countInstances(entity) >= entity.getMaxInstanceLimit()) {
if (entity.hasPrimaryKeyField()) {
Field primaryField = entity.getPrimaryKeyField();
if (!instance.hasInstantiatedFieldNamed(primaryField.getName())) {
throw new RuntimeException(
String.format(
"ERROR: Cannot add instance, primary key field %s not set",
primaryField.getName()));
throw ThingStoreWriteException.missingPrimaryKey(entity, primaryField.getName());
}

EntityInstance existing =
findInstanceByPrimaryKey(entity, instance.getPrimaryKeyValue());
if (existing != null && !existing.getInternalId().equals(instance.getInternalId())) {
throw new RuntimeException(
"ERROR: Cannot add instance, another instance with primary key value exists: "
+ existing.getPrimaryKeyValue());
throw ThingStoreWriteException.duplicatePrimaryKey(
entity, existing.getPrimaryKeyValue());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,80 @@ public void sqliteRepositoryGeneratesAutoIdsThroughTheContract() {
}
}

@Test
public void sqliteRepositoryThrowsTypedMaxInstanceLimitFailure() {
ERSchema schema = ticketSchema(1);
EntityDefinition ticket = schema.getEntityDefinitionNamed("ticket");

try (ThingStore repository =
SqliteThingStore.inMemory(EntityRelModel.DEFAULT_DATABASE_NAME)) {
repository.administration().initializeFrom(schema);
createWithId(repository, ticket, "one");

ThingStoreWriteException exception =
Assertions.assertThrows(
ThingStoreWriteException.class,
() -> createWithId(repository, ticket, "two"));

Assertions.assertEquals(
ThingStoreWriteException.Reason.MAX_INSTANCE_LIMIT_REACHED, exception.reason());
Assertions.assertEquals(
"ERROR: Cannot add instance, maximum limit of 1 reached",
exception.getMessage());
}
}

@Test
public void sqliteRepositoryThrowsTypedDuplicatePrimaryKeyFailure() {
ERSchema schema = ticketSchema(-1);
EntityDefinition ticket = schema.getEntityDefinitionNamed("ticket");

try (ThingStore repository =
SqliteThingStore.inMemory(EntityRelModel.DEFAULT_DATABASE_NAME)) {
repository.administration().initializeFrom(schema);
createWithId(repository, ticket, "same");

ThingStoreWriteException exception =
Assertions.assertThrows(
ThingStoreWriteException.class,
() -> createWithId(repository, ticket, "same"));

Assertions.assertEquals(
ThingStoreWriteException.Reason.DUPLICATE_PRIMARY_KEY, exception.reason());
Assertions.assertEquals(
"ERROR: Cannot add instance, another instance with primary key value exists: "
+ "same",
exception.getMessage());
}
}

@Test
public void sqliteRepositoryThrowsTypedMissingPrimaryKeyFailure() {
ERSchema schema = ticketSchema(-1);
EntityDefinition ticket = schema.getEntityDefinitionNamed("ticket");

try (ThingStore repository =
SqliteThingStore.inMemory(EntityRelModel.DEFAULT_DATABASE_NAME)) {
repository.administration().initializeFrom(schema);

ThingStoreWriteException exception =
Assertions.assertThrows(
ThingStoreWriteException.class,
() ->
repository
.entities()
.create(
EntityInstanceDraft.forEntity(ticket)
.withField("title", "Missing id")));

Assertions.assertEquals(
ThingStoreWriteException.Reason.MISSING_PRIMARY_KEY, exception.reason());
Assertions.assertEquals(
"ERROR: Cannot add instance, primary key field id not set",
exception.getMessage());
}
}

@Test
public void inMemoryRepositoryOwnsRelationshipValidationAndCascade() {
ThingStore repository = new InMemoryThingStore(EntityRelModel.DEFAULT_DATABASE_NAME);
Expand Down Expand Up @@ -854,6 +928,16 @@ private EntityInstance create(
.create(EntityInstanceDraft.forEntity(entity).withField("title", title));
}

private EntityInstance createWithId(
final ThingStore repository, final EntityDefinition entity, final String id) {
return repository
.entities()
.create(
EntityInstanceDraft.forEntity(entity)
.withField("id", id)
.withField("title", "Title " + id));
}

private String exportDataAsJson(final ThingStore repository, final ERSchema schema) {
return new RepositoryJsonExporter(schema, repository.entityQueries()).asJson();
}
Expand All @@ -870,6 +954,16 @@ private ERSchema autoIdSchema() {
return schema;
}

private ERSchema ticketSchema(final int maxInstances) {
ERSchema schema = new ERSchema();

EntityDefinition ticket = schema.defineEntity("ticket", "tickets", maxInstances);
ticket.addAsPrimaryKeyField(Field.is("id", FieldType.STRING));
ticket.addField(Field.is("title", FieldType.STRING));

return schema;
}

private void assertExportedJsonContainsProjectAndTask(final String json) {
Assertions.assertTrue(json.contains("\"projects\""));
Assertions.assertTrue(json.contains("\"tasks\""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceDraft;
import uk.co.compendiumdev.thingifier.core.repository.MutableEntityInstance;
import uk.co.compendiumdev.thingifier.core.repository.ThingStoreWriteException;

public class InMemoryEntityInstanceCollectionTest {

Expand All @@ -33,10 +34,12 @@ public void cannotCreateInstanceWithoutPrimaryKeySet() {
MutableEntityInstance instance1 =
MutableEntityInstance.fromDraft(EntityInstanceDraft.forEntity(entityDefn));

Exception exception =
ThingStoreWriteException exception =
Assertions.assertThrows(
RuntimeException.class, () -> collection.addInstance(instance1));
ThingStoreWriteException.class, () -> collection.addInstance(instance1));

Assertions.assertEquals(
ThingStoreWriteException.Reason.MISSING_PRIMARY_KEY, exception.reason());
Assertions.assertTrue(
exception
.getMessage()
Expand All @@ -60,10 +63,12 @@ public void cannotCreateInstanceWithDuplicatePrimaryKey() {
EntityInstanceDraft.forEntity(entityDefn)
.withField("pk", instance1.getPrimaryKeyValue()));

Exception exception =
ThingStoreWriteException exception =
Assertions.assertThrows(
RuntimeException.class, () -> collection.addInstance(instance2));
ThingStoreWriteException.class, () -> collection.addInstance(instance2));

Assertions.assertEquals(
ThingStoreWriteException.Reason.DUPLICATE_PRIMARY_KEY, exception.reason());
Assertions.assertTrue(
exception.getMessage().contains("another instance with primary key value exists"));
}
Expand Down
Loading
Loading