Skip to content

Commit

Permalink
Braking schema cycle by pairs of related entities
Browse files Browse the repository at this point in the history
  • Loading branch information
javiertuya committed Mar 22, 2024
1 parent 63c069f commit 577424b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
public class SchemaSorter {
private static final Logger log = LoggerFactory.getLogger(SchemaSorter.class);
private TdSchema schema;
private List<String> excludeConstraints = new ArrayList<>();
private List<String> excludeRidnames = new ArrayList<>();
private List<String[]> excludeRids = new ArrayList<>(); // source and target entity
private boolean arrayHoist = false;

public SchemaSorter(TdSchema schema) {
Expand All @@ -42,11 +43,22 @@ public SchemaSorter(TdSchema schema) {
* be ignored during sorting (to avoid cycles). Several calls to this function
* can be concatenated to add more than one constraint
*/
public SchemaSorter noFollowConstraint(String constraintName) {
excludeConstraints.add(constraintName.toLowerCase());
public SchemaSorter noFollowRidname(String constraintName) {
excludeRidnames.add(constraintName.toLowerCase());
return this; // fluent to concatenate if more than one constraint
}

/**
* Specifies a relation between two entities given by their names that will
* be ignored during sorting (to avoid cycles). Several calls to this function
* can be concatenated to add more than one constraint.
* Example parameters a, b indicate that all rids from a to b must be ignored
*/
public SchemaSorter noFollowRid(String fromEntity, String toEntity) {
excludeRids.add(new String[] { fromEntity, toEntity });
return this;
}

/**
* When array hoist is specified, the entities derived from arrays are prepended
* to the entity that contains the array, despite the array rid references the
Expand Down Expand Up @@ -133,7 +145,7 @@ private String getDependency(String entityName, TdAttribute attribute, List<Stri
// and there is a true reference (handled in separate method)
&& attributeReferencesAnyOf(entityName, attribute, originalEntities)
// but not excluded to break cycles
&& !excludeConstraints.contains(attribute.getRidname().toLowerCase());
&& !excludedRelation(entityName, attribute);
if (dependentByFk)
return attribute.getRidEntity();

Expand All @@ -148,6 +160,18 @@ && attributeReferencesAnyOf(entityName, attribute, originalEntities)

return null;
}

// Subrules to determine a reference should be excluded (to break cycles)
private boolean excludedRelation(String entityName, TdAttribute attribute) {
// check exclusion criterion: by ridname
if (excludeRidnames.contains(attribute.getRidname().toLowerCase()))
return true;
// check exclusion criterion: by a pair of related entities
for (String[] rid : excludeRids)
if (rid[0].equalsIgnoreCase(entityName) && rid[1].equalsIgnoreCase(attribute.getRidEntity()))
return true;
return false;
}

// Subrules to determine if the rid of an attribute is referencing one of the entities in a list
private boolean attributeReferencesAnyOf(String entityName, TdAttribute attribute, List<String> entities) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
*/
public class TestSchemaSorter extends Base {

private static final String TOO_MANY_RECUSIVE_LEVELS = "Too many recusive levels when trying sort entities";

// simple diamond shape with a recursive relation
private TdSchema getModelBase() {
TdEntity bottom = new TdEntity().name("bottom")
Expand Down Expand Up @@ -70,22 +72,41 @@ public void testSortPartialSchema() {
}

@Test
public void testSortCycles() {
public void testSortCyclesByRidname() {
TdSchema schema = getModelBase();
addReference(schema, "top", "bottom.id", "fk_recurse", "constraint_name");

// Detection of cycles
ModelException exception = assertThrows(ModelException.class, () -> {
new SchemaSorter(schema).sort(Arrays.asList("bottom", "right", "left", "top"));
});
assertEquals("Too many recusive levels when trying sort entities", exception.getMessage());
assertEquals(TOO_MANY_RECUSIVE_LEVELS, exception.getMessage());

// Do not throw if we break the cycle
SchemaSorter sorter = new SchemaSorter(schema).noFollowConstraint("constraint_name");
// Do not throw if we break the cycle by specifying the constraint
SchemaSorter sorter = new SchemaSorter(schema).noFollowRidname("Constraint_Name");
assertEquals("[top, left, right, bottom]",
sorter.sort(Arrays.asList("bottom", "right", "left", "top")).toString());
}

@Test
public void testSortCyclesByPairOfEntities() {
TdSchema schema = getModelBase();
addReference(schema, "top", "bottom.id", "fk_recurse", "");

// Do not throw if we break the cycle by specifying the pair of entities
SchemaSorter sorter = new SchemaSorter(schema).noFollowRid("Top", "Bottom");
assertEquals("[top, left, right, bottom]",
sorter.sort(Arrays.asList("bottom", "right", "left", "top")).toString());

// The inverse relation does not exist, also if only one entity matches, do not break the cycle
SchemaSorter sorterCycle = new SchemaSorter(schema).noFollowRid("bottom", "top")
.noFollowRid("top", "xxx").noFollowRid("yyy", "bottom");
ModelException exception = assertThrows(ModelException.class, () -> {
sorterCycle.sort(Arrays.asList("bottom", "right", "left", "top"));
});
assertEquals(TOO_MANY_RECUSIVE_LEVELS, exception.getMessage());
}

@Test
public void testFindDependentEntities() {
SchemaSorter sorter = new SchemaSorter(getModelBase());
Expand Down

0 comments on commit 577424b

Please # to comment.