Commit f59284ad by Sarath Subramanian

ATLAS-2040: Relationship with many-to-many cardinality gives incorrect relationship attribute value

parent 1b7e41f1
......@@ -31,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.BOTH;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
......@@ -118,24 +119,33 @@ public class AtlasRelationshipType extends AtlasStructType {
}
private void addRelationshipEdgeDirection() {
AtlasRelationshipEndDef endDef1 = relationshipDef.getEndDef1();
AtlasRelationshipEndDef endDef2 = relationshipDef.getEndDef2();
AtlasAttribute end1Attribute = end1Type.getRelationshipAttribute(endDef1.getName());
AtlasAttribute end2Attribute = end2Type.getRelationshipAttribute(endDef2.getName());
AtlasRelationshipEndDef endDef1 = relationshipDef.getEndDef1();
AtlasRelationshipEndDef endDef2 = relationshipDef.getEndDef2();
//default relationship edge direction is end1 (out) -> end2 (in)
AtlasRelationshipEdgeDirection end1Direction = OUT;
AtlasRelationshipEdgeDirection end2Direction = IN;
if (StringUtils.equals(endDef1.getType(), endDef2.getType()) &&
StringUtils.equals(endDef1.getName(), endDef2.getName())) {
if (endDef1.getIsLegacyAttribute() && endDef2.getIsLegacyAttribute()) {
end2Direction = OUT;
} else if (!endDef1.getIsLegacyAttribute() && endDef2.getIsLegacyAttribute()) {
end1Direction = IN;
end2Direction = OUT;
}
AtlasAttribute endAttribute = end1Type.getRelationshipAttribute(endDef1.getName());
end1Attribute.setRelationshipEdgeDirection(end1Direction);
end2Attribute.setRelationshipEdgeDirection(end2Direction);
endAttribute.setRelationshipEdgeDirection(BOTH);
} else {
AtlasAttribute end1Attribute = end1Type.getRelationshipAttribute(endDef1.getName());
AtlasAttribute end2Attribute = end2Type.getRelationshipAttribute(endDef2.getName());
//default relationship edge direction is end1 (out) -> end2 (in)
AtlasRelationshipEdgeDirection end1Direction = OUT;
AtlasRelationshipEdgeDirection end2Direction = IN;
if (endDef1.getIsLegacyAttribute() && endDef2.getIsLegacyAttribute()) {
end2Direction = OUT;
} else if (!endDef1.getIsLegacyAttribute() && endDef2.getIsLegacyAttribute()) {
end1Direction = IN;
end2Direction = OUT;
}
end1Attribute.setRelationshipEdgeDirection(end1Direction);
end2Attribute.setRelationshipEdgeDirection(end2Direction);
}
}
@Override
......@@ -200,7 +210,6 @@ public class AtlasRelationshipType extends AtlasStructType {
AtlasRelationshipEndDef endDef2 = relationshipDef.getEndDef2();
RelationshipCategory relationshipCategory = relationshipDef.getRelationshipCategory();
String name = relationshipDef.getName();
boolean isContainer1 = endDef1.getIsContainer();
boolean isContainer2 = endDef2.getIsContainer();
......
......@@ -794,6 +794,6 @@ public class AtlasStructType extends AtlasType {
private static final char DOUBLE_QUOTE_CHAR = '"';
private static final char SPACE_CHAR = ' ';
public enum AtlasRelationshipEdgeDirection { IN, OUT }
public enum AtlasRelationshipEdgeDirection { IN, OUT, BOTH }
}
}
......@@ -42,7 +42,6 @@ import org.apache.atlas.repository.graphdb.AtlasVertex;
import org.apache.atlas.repository.store.graph.v1.AtlasGraphUtilsV1;
import org.apache.atlas.type.AtlasEntityType;
import org.apache.atlas.type.AtlasRelationshipType;
import org.apache.atlas.type.AtlasStructType;
import org.apache.atlas.type.AtlasType;
import org.apache.atlas.typesystem.IReferenceableInstance;
import org.apache.atlas.typesystem.ITypedInstance;
......@@ -86,6 +85,10 @@ import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.BOTH;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
/**
* Utility class for graph operations.
*/
......@@ -308,7 +311,7 @@ public final class GraphHelper {
//In some cases of parallel APIs, the edge is added, but get edge by label doesn't return the edge. ATLAS-1104
//So traversing all the edges
public Iterator<AtlasEdge> getAdjacentEdgesByLabel(AtlasVertex instanceVertex, AtlasEdgeDirection direction, final String edgeLabel) {
public static Iterator<AtlasEdge> getAdjacentEdgesByLabel(AtlasVertex instanceVertex, AtlasEdgeDirection direction, final String edgeLabel) {
if (LOG.isDebugEnabled()) {
LOG.debug("Finding edges for {} with label {}", string(instanceVertex), edgeLabel);
}
......@@ -348,11 +351,11 @@ public final class GraphHelper {
return null;
}
public Iterator<AtlasEdge> getIncomingEdgesByLabel(AtlasVertex instanceVertex, String edgeLabel) {
public static Iterator<AtlasEdge> getIncomingEdgesByLabel(AtlasVertex instanceVertex, String edgeLabel) {
return getAdjacentEdgesByLabel(instanceVertex, AtlasEdgeDirection.IN, edgeLabel);
}
public Iterator<AtlasEdge> getOutGoingEdgesByLabel(AtlasVertex instanceVertex, String edgeLabel) {
public static Iterator<AtlasEdge> getOutGoingEdgesByLabel(AtlasVertex instanceVertex, String edgeLabel) {
return getAdjacentEdgesByLabel(instanceVertex, AtlasEdgeDirection.OUT, edgeLabel);
}
......@@ -361,20 +364,24 @@ public final class GraphHelper {
switch (edgeDirection) {
case IN:
ret = getEdgeForLabel(vertex, edgeLabel, AtlasEdgeDirection.IN);
break;
ret = getEdgeForLabel(vertex, edgeLabel, AtlasEdgeDirection.IN);
break;
case OUT:
ret = getEdgeForLabel(vertex, edgeLabel, AtlasEdgeDirection.OUT);
break;
case BOTH:
default:
ret = getEdgeForLabel(vertex, edgeLabel, AtlasEdgeDirection.OUT);
ret = getEdgeForLabel(vertex, edgeLabel, AtlasEdgeDirection.BOTH);
break;
}
return ret;
}
public Iterator<AtlasEdge> getEdgesForLabel(AtlasVertex vertex, String edgeLabel, AtlasRelationshipEdgeDirection edgeDirection) {
Iterator<AtlasEdge> ret;
public static Iterator<AtlasEdge> getEdgesForLabel(AtlasVertex vertex, String edgeLabel, AtlasRelationshipEdgeDirection edgeDirection) {
Iterator<AtlasEdge> ret = null;
switch (edgeDirection) {
case IN:
......@@ -382,8 +389,11 @@ public final class GraphHelper {
break;
case OUT:
default:
ret = getOutGoingEdgesByLabel(vertex, edgeLabel);
ret = getOutGoingEdgesByLabel(vertex, edgeLabel);
break;
case BOTH:
ret = getAdjacentEdgesByLabel(vertex, AtlasEdgeDirection.BOTH, edgeLabel);
break;
}
......@@ -1341,32 +1351,38 @@ public final class GraphHelper {
return StringUtils.isNotEmpty(edge.getLabel()) ? edgeLabel.startsWith("r:") : false;
}
public static AtlasObjectId getReferenceObjectId(AtlasEdge edge, AtlasRelationshipEdgeDirection relationshipDirection) {
public static AtlasObjectId getReferenceObjectId(AtlasEdge edge, AtlasRelationshipEdgeDirection relationshipDirection,
AtlasVertex parentVertex) {
AtlasObjectId ret = null;
if (relationshipDirection == AtlasRelationshipEdgeDirection.OUT) {
ret = new AtlasObjectId(getGuid(edge.getInVertex()), getTypeName(edge.getInVertex()));
if (relationshipDirection == OUT) {
ret = getAtlasObjectIdForInVertex(edge);
} else if (relationshipDirection == IN) {
ret = getAtlasObjectIdForOutVertex(edge);
} else if (relationshipDirection == AtlasRelationshipEdgeDirection.IN) {
ret = new AtlasObjectId(getGuid(edge.getOutVertex()), getTypeName(edge.getOutVertex()));
} else if (relationshipDirection == BOTH){
// since relationship direction is BOTH, edge direction can be inward or outward
// compare with parent entity vertex and pick the right reference vertex
if (verticesEquals(parentVertex, edge.getOutVertex())) {
ret = getAtlasObjectIdForInVertex(edge);
} else {
ret = getAtlasObjectIdForOutVertex(edge);
}
}
return ret;
}
public static AtlasObjectId getCurrentObjectId(AtlasEdge edge, AtlasRelationshipEdgeDirection relationshipDirection) {
String typeName = null;
String guid = null;
if (relationshipDirection == AtlasRelationshipEdgeDirection.OUT) {
typeName = GraphHelper.getTypeName(edge.getOutVertex());
guid = GraphHelper.getGuid(edge.getOutVertex());
public static AtlasObjectId getAtlasObjectIdForOutVertex(AtlasEdge edge) {
return new AtlasObjectId(getGuid(edge.getOutVertex()), getTypeName(edge.getOutVertex()));
}
} else if (relationshipDirection == AtlasRelationshipEdgeDirection.IN) {
typeName = GraphHelper.getTypeName(edge.getInVertex());
guid = GraphHelper.getGuid(edge.getInVertex());
}
public static AtlasObjectId getAtlasObjectIdForInVertex(AtlasEdge edge) {
return new AtlasObjectId(getGuid(edge.getInVertex()), getTypeName(edge.getInVertex()));
}
return new AtlasObjectId(guid, typeName);
private static boolean verticesEquals(AtlasVertex vertexA, AtlasVertex vertexB) {
return StringUtils.equals(getGuid(vertexB), getGuid(vertexA));
}
}
\ No newline at end of file
......@@ -40,6 +40,7 @@ import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
import org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection;
import org.apache.atlas.type.AtlasType;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -54,9 +55,13 @@ import java.util.Set;
import java.util.Stack;
import static org.apache.atlas.repository.graph.GraphHelper.EDGE_LABEL_PREFIX;
import static org.apache.atlas.repository.graph.GraphHelper.getAtlasObjectIdForInVertex;
import static org.apache.atlas.repository.graph.GraphHelper.getAtlasObjectIdForOutVertex;
import static org.apache.atlas.repository.graph.GraphHelper.getGuid;
import static org.apache.atlas.repository.graph.GraphHelper.getReferenceObjectId;
import static org.apache.atlas.repository.graph.GraphHelper.isRelationshipEdge;
import static org.apache.atlas.repository.graph.GraphHelper.string;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.BOTH;
public abstract class DeleteHandlerV1 {
......@@ -219,14 +224,14 @@ public abstract class DeleteHandlerV1 {
* @throws AtlasException
*/
public boolean deleteEdgeReference(AtlasEdge edge, TypeCategory typeCategory, boolean isOwned,
boolean forceDeleteStructTrait) throws AtlasBaseException {
boolean forceDeleteStructTrait, AtlasVertex vertex) throws AtlasBaseException {
// default edge direction is outward
return deleteEdgeReference(edge, typeCategory, isOwned, forceDeleteStructTrait, AtlasRelationshipEdgeDirection.OUT);
return deleteEdgeReference(edge, typeCategory, isOwned, forceDeleteStructTrait, AtlasRelationshipEdgeDirection.OUT, vertex);
}
public boolean deleteEdgeReference(AtlasEdge edge, TypeCategory typeCategory, boolean isOwned, boolean forceDeleteStructTrait,
AtlasRelationshipEdgeDirection relationshipDirection) throws AtlasBaseException {
AtlasRelationshipEdgeDirection relationshipDirection, AtlasVertex entityVertex) throws AtlasBaseException {
LOG.debug("Deleting {}", string(edge));
boolean forceDelete =
(typeCategory == TypeCategory.STRUCT || typeCategory == TypeCategory.CLASSIFICATION) && forceDeleteStructTrait;
......@@ -250,7 +255,7 @@ public abstract class DeleteHandlerV1 {
if (isRelationshipEdge(edge)) {
deleteEdge(edge, false);
AtlasObjectId deletedReferenceObjectId = getReferenceObjectId(edge, relationshipDirection);
AtlasObjectId deletedReferenceObjectId = getReferenceObjectId(edge, relationshipDirection, entityVertex);
RequestContextV1.get().recordEntityUpdate(deletedReferenceObjectId);
} else {
//legacy case - not a relationship edge
......@@ -344,7 +349,7 @@ public abstract class DeleteHandlerV1 {
if (edges != null) {
while (edges.hasNext()) {
AtlasEdge edge = edges.next();
deleteEdgeReference(edge, elemType.getTypeCategory(), isOwned, false);
deleteEdgeReference(edge, elemType.getTypeCategory(), isOwned, false, instanceVertex);
}
}
}
......@@ -377,7 +382,7 @@ public abstract class DeleteHandlerV1 {
boolean isOwned) throws AtlasBaseException {
AtlasEdge edge = graphHelper.getEdgeForLabel(outVertex, edgeLabel);
if (edge != null) {
deleteEdgeReference(edge, typeCategory, isOwned, false);
deleteEdgeReference(edge, typeCategory, isOwned, false, outVertex);
}
}
......
......@@ -72,6 +72,9 @@ import static org.apache.atlas.model.typedef.AtlasBaseTypeDef.ATLAS_TYPE_STRING;
import static org.apache.atlas.repository.graph.GraphHelper.EDGE_LABEL_PREFIX;
import static org.apache.atlas.repository.store.graph.v1.AtlasGraphUtilsV1.getIdFromVertex;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.BOTH;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
public final class EntityGraphRetriever {
......@@ -683,10 +686,12 @@ public final class EntityGraphRetriever {
List<AtlasRelatedObjectId> ret = new ArrayList<>();
Iterator<AtlasEdge> edges = null;
if (attribute.getRelationshipEdgeDirection() == AtlasRelationshipEdgeDirection.IN) {
if (attribute.getRelationshipEdgeDirection() == IN) {
edges = graphHelper.getIncomingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel());
} else if (attribute.getRelationshipEdgeDirection() == AtlasRelationshipEdgeDirection.OUT) {
} else if (attribute.getRelationshipEdgeDirection() == OUT) {
edges = graphHelper.getOutGoingEdgesByLabel(entityVertex, attribute.getRelationshipEdgeLabel());
} else if (attribute.getRelationshipEdgeDirection() == BOTH) {
edges = graphHelper.getAdjacentEdgesByLabel(entityVertex, AtlasEdgeDirection.BOTH, attribute.getRelationshipEdgeLabel());
}
if (edges != null) {
......
......@@ -20,9 +20,15 @@ package org.apache.atlas.repository.store.graph.v1;
import com.google.common.collect.ImmutableList;
import org.apache.atlas.TestModules;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.testng.annotations.Guice;
import java.util.List;
import static org.apache.atlas.type.AtlasTypeUtil.getAtlasObjectId;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
/**
* Inverse reference update test with {@link HardDeleteHandlerV1}
......@@ -51,4 +57,56 @@ public class AtlasRelationshipStoreHardDeleteV1Test extends AtlasRelationshipSto
protected void verifyRelationshipAttributeUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) {
verifyRelationshipAttributeValue(a1, "b", null);
}
}
@Override
protected void verifyRelationshipAttributeUpdate_ManyToMany_Friends(AtlasEntity max, AtlasEntity julius, AtlasEntity mike, AtlasEntity john) throws Exception {
AtlasObjectId johnId = employeeNameIdMap.get("John");
AtlasObjectId mikeId = employeeNameIdMap.get("Mike");
AtlasObjectId juliusId = employeeNameIdMap.get("Julius");
AtlasObjectId maxId = employeeNameIdMap.get("Max");
List<AtlasObjectId> maxFriendsIds = toAtlasObjectIds(max.getRelationshipAttribute("friends"));
assertNotNull(maxFriendsIds);
assertEquals(maxFriendsIds.size(), 2);
assertObjectIdsContains(maxFriendsIds, johnId);
assertObjectIdsContains(maxFriendsIds, juliusId);
// Julius's updated friends: [Max]
List<AtlasObjectId> juliusFriendsIds = toAtlasObjectIds(julius.getRelationshipAttribute("friends"));
assertNotNull(juliusFriendsIds);
assertEquals(juliusFriendsIds.size(), 1);
assertObjectIdsContains(juliusFriendsIds, maxId);
// Mike's updated friends: [John]
List<AtlasObjectId> mikeFriendsIds = toAtlasObjectIds(mike.getRelationshipAttribute("friends"));
assertNotNull(mikeFriendsIds);
assertEquals(mikeFriendsIds.size(), 1);
assertObjectIdsContains(mikeFriendsIds, johnId);
// John's updated friends: [Max, Mike]
List<AtlasObjectId> johnFriendsIds = toAtlasObjectIds(john.getRelationshipAttribute("friends"));
assertNotNull(johnFriendsIds);
assertEquals(johnFriendsIds.size(), 2);
assertObjectIdsContains(johnFriendsIds, maxId);
assertObjectIdsContains(johnFriendsIds, mikeId);
}
protected void verifyRelationshipAttributeUpdate_OneToOne_Sibling(AtlasEntity julius, AtlasEntity jane, AtlasEntity mike) throws Exception {
AtlasObjectId juliusId = employeeNameIdMap.get("Julius");
AtlasObjectId mikeId = employeeNameIdMap.get("Mike");
// Julius sibling updated to Mike
AtlasObjectId juliusSiblingId = toAtlasObjectId(julius.getRelationshipAttribute("sibling"));
assertNotNull(juliusSiblingId);
assertObjectIdEquals(juliusSiblingId, mikeId);
// Mike's sibling is Julius
AtlasObjectId mikeSiblingId = toAtlasObjectId(mike.getRelationshipAttribute("sibling"));
assertNotNull(mikeSiblingId);
assertObjectIdEquals(mikeSiblingId, juliusId);
// Julius removed from Jane's sibling (hard delete)
AtlasObjectId janeSiblingId = toAtlasObjectId(jane.getRelationshipAttribute("sibling"));
assertNull(janeSiblingId);
}
}
\ No newline at end of file
......@@ -20,9 +20,14 @@ package org.apache.atlas.repository.store.graph.v1;
import com.google.common.collect.ImmutableList;
import org.apache.atlas.TestModules;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.testng.annotations.Guice;
import java.util.List;
import static org.apache.atlas.type.AtlasTypeUtil.getAtlasObjectId;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
/**
......@@ -52,4 +57,60 @@ public class AtlasRelationshipStoreSoftDeleteV1Test extends AtlasRelationshipSto
protected void verifyRelationshipAttributeUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) {
verifyRelationshipAttributeValue(a1, "b", b.getGuid());
}
}
@Override
protected void verifyRelationshipAttributeUpdate_ManyToMany_Friends(AtlasEntity max, AtlasEntity julius, AtlasEntity mike, AtlasEntity john) throws Exception {
AtlasObjectId johnId = employeeNameIdMap.get("John");
AtlasObjectId mikeId = employeeNameIdMap.get("Mike");
AtlasObjectId juliusId = employeeNameIdMap.get("Julius");
AtlasObjectId maxId = employeeNameIdMap.get("Max");
// Max's updated friends: [Julius, John, Mike(soft deleted)]
List<AtlasObjectId> maxFriendsIds = toAtlasObjectIds(max.getRelationshipAttribute("friends"));
assertNotNull(maxFriendsIds);
assertEquals(maxFriendsIds.size(), 3);
assertObjectIdsContains(maxFriendsIds, johnId);
assertObjectIdsContains(maxFriendsIds, juliusId);
assertObjectIdsContains(maxFriendsIds, mikeId);
// Julius's updated friends: [Max]
List<AtlasObjectId> juliusFriendsIds = toAtlasObjectIds(julius.getRelationshipAttribute("friends"));
assertNotNull(juliusFriendsIds);
assertEquals(juliusFriendsIds.size(), 1);
assertObjectIdsContains(juliusFriendsIds, maxId);
// Mike's updated friends: [John, Max(soft deleted)]
List<AtlasObjectId> mikeFriendsIds = toAtlasObjectIds(mike.getRelationshipAttribute("friends"));
assertNotNull(mikeFriendsIds);
assertEquals(mikeFriendsIds.size(), 2);
assertObjectIdsContains(mikeFriendsIds, johnId);
assertObjectIdsContains(mikeFriendsIds, maxId);
// John's updated friends: [Max, Mike]
List<AtlasObjectId> johnFriendsIds = toAtlasObjectIds(john.getRelationshipAttribute("friends"));
assertNotNull(johnFriendsIds);
assertEquals(johnFriendsIds.size(), 2);
assertObjectIdsContains(johnFriendsIds, maxId);
assertObjectIdsContains(johnFriendsIds, mikeId);
}
protected void verifyRelationshipAttributeUpdate_OneToOne_Sibling(AtlasEntity julius, AtlasEntity jane, AtlasEntity mike) throws Exception {
AtlasObjectId juliusId = employeeNameIdMap.get("Julius");
AtlasObjectId mikeId = employeeNameIdMap.get("Mike");
// Julius sibling updated to Mike
AtlasObjectId juliusSiblingId = toAtlasObjectId(julius.getRelationshipAttribute("sibling"));
assertNotNull(juliusSiblingId);
assertObjectIdEquals(juliusSiblingId, mikeId);
// Mike's sibling is Julius
AtlasObjectId mikeSiblingId = toAtlasObjectId(mike.getRelationshipAttribute("sibling"));
assertNotNull(mikeSiblingId);
assertObjectIdEquals(mikeSiblingId, juliusId);
// Jane's sibling is still Julius (soft delete)
AtlasObjectId janeSiblingId = toAtlasObjectId(jane.getRelationshipAttribute("sibling"));
assertNotNull(janeSiblingId);
assertObjectIdEquals(janeSiblingId, juliusId);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment