Skip to content
Draft
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
6 changes: 3 additions & 3 deletions addons/models/6000-Trino/6000-trino_model.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
"relationshipCategory": "AGGREGATION",
"propagateTags": "NONE",
"endDef1": { "type": "trino_table", "name": "trinoschema", "isContainer": false, "cardinality": "SINGLE", "isLegacyAttribute": false },
"endDef2": { "type": "trino_schema", "name": "tables", "isContainer": true, "cardinality": "SET" }
"endDef2": { "type": "trino_schema", "name": "tables", "isContainer": true, "cardinality": "SET", "propagateDelete": true }
},
{
"name": "trino_table_columns",
Expand Down Expand Up @@ -172,7 +172,7 @@
"typeVersion": "1.0",
"relationshipCategory": "ASSOCIATION",
"propagateTags": "BOTH",
"endDef1": { "type": "hive_db", "name": "trino_schema", "cardinality": "SET" },
"endDef1": { "type": "hive_db", "name": "trino_schema", "cardinality": "SET", "propagateDelete": true },
"endDef2": { "type": "trino_schema", "name": "hive_db", "cardinality": "SINGLE" }
},
{
Expand All @@ -181,7 +181,7 @@
"typeVersion": "1.0",
"relationshipCategory": "ASSOCIATION",
"propagateTags": "BOTH",
"endDef1": { "type": "hive_table", "name": "trino_table", "cardinality": "SET", "propagateRename": true, "propagateAttributes": [ { "source": "name", "target": "name" } ] },
"endDef1": { "type": "hive_table", "name": "trino_table", "cardinality": "SET", "propagateRename": true, "propagateDelete": true, "propagateAttributes": [ { "source": "name", "target": "name" } ] },
"endDef2": { "type": "trino_table", "name": "hive_table", "cardinality": "SINGLE" }
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"patches": [
{
"id": "TYPEDEF_PATCH_6000_002_A",
"description": "Set propagateDelete=true on trino_table_hive_table endDef1 (hive_table → trino_table)",
"action": "SET_PROPAGATE_DELETE",
"typeName": "trino_table_hive_table",
"applyToVersion": "1.1",
"updateToVersion": "1.2",
"params": {
"endDefToken": "endDef1"
}
},
{
"id": "TYPEDEF_PATCH_6000_002_B",
"description": "Set propagateDelete=true on trino_schema_hive_db endDef1 (hive_db → trino_schema)",
"action": "SET_PROPAGATE_DELETE",
"typeName": "trino_schema_hive_db",
"applyToVersion": "1.0",
"updateToVersion": "1.1",
"params": {
"endDefToken": "endDef1"
}
},
{
"id": "TYPEDEF_PATCH_6000_002_C",
"description": "Set propagateDelete=true on trino_table_schema endDef2 (trino_schema → trino_table)",
"action": "SET_PROPAGATE_DELETE",
"typeName": "trino_table_schema",
"applyToVersion": "1.0",
"updateToVersion": "1.1",
"params": {
"endDefToken": "endDef2"
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ public class AtlasRelationshipEndDef implements Serializable {
* entity {@code name} attribute is synchronized.
*/
private List<Map<String, String>> propagateAttributes;
/**
* When set, deleting an entity of this end's type cascades the delete to entities on the
* other end of the relationship. Use with caution: enabling this flag means ALL related
* entities reachable via this relationship will be deleted — the effect is recursive if
* the target type also has propagateDelete configured on its relationships. This is
* irreversible in a single transaction; ensure the modeled semantics truly require cascade
* (e.g., the target entity cannot meaningfully exist without the source).
*/
private boolean propagateDelete;

/**
* Base constructor
Expand Down Expand Up @@ -177,6 +186,7 @@ public AtlasRelationshipEndDef(AtlasRelationshipEndDef other) {
setDescription(other.description);
setIsPropagateRename(other.propagateRename);
setPropagateAttributes(other.propagateAttributes);
setIsPropagateDelete(other.propagateDelete);
}
}

Expand Down Expand Up @@ -254,14 +264,15 @@ public StringBuilder toString(StringBuilder sb) {
sb.append(", isLegacyAttribute==>'").append(isLegacyAttribute).append('\'');
sb.append(", propagateRename==>'").append(propagateRename).append('\'');
sb.append(", propagateAttributes==>'").append(propagateAttributes).append('\'');
sb.append(", propagateDelete==>'").append(propagateDelete).append('\'');
sb.append('}');

return sb;
}

@Override
public int hashCode() {
return Objects.hash(type, getName(), description, isContainer, cardinality, isLegacyAttribute, propagateRename, propagateAttributes);
return Objects.hash(type, getName(), description, isContainer, cardinality, isLegacyAttribute, propagateRename, propagateAttributes, propagateDelete);
}

@Override
Expand All @@ -281,7 +292,8 @@ public boolean equals(Object o) {
cardinality == that.cardinality &&
isLegacyAttribute == that.isLegacyAttribute &&
propagateRename == that.propagateRename &&
Objects.equals(propagateAttributes, that.propagateAttributes);
Objects.equals(propagateAttributes, that.propagateAttributes) &&
propagateDelete == that.propagateDelete;
}

@JsonProperty("propagateRename")
Expand All @@ -302,6 +314,16 @@ public void setPropagateAttributes(List<Map<String, String>> propagateAttributes
this.propagateAttributes = (propagateAttributes != null) ? Collections.unmodifiableList(propagateAttributes) : null;
}

@JsonProperty("propagateDelete")
public boolean getIsPropagateDelete() {
return propagateDelete;
}

@JsonProperty("propagateDelete")
public void setIsPropagateDelete(boolean propagateDelete) {
this.propagateDelete = propagateDelete;
}

@Override
public String toString() {
return toString(new StringBuilder()).toString();
Expand Down
57 changes: 57 additions & 0 deletions intg/src/main/java/org/apache/atlas/type/AtlasEntityType.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public class AtlasEntityType extends AtlasStructType {
* are resolved from the model.
*/
private List<RenamePropagationTarget> renamePropagationTargets = Collections.emptyList();
private List<DeletePropagationTarget> deletePropagationTargets = Collections.emptyList();
/**
* For this type's qualifiedName {@code autoComputeFormat}: each key is another entity type name
* that appears in the template (via relationship hops ending in {@code .name}); the value is the
Expand Down Expand Up @@ -267,6 +268,10 @@ public List<RenamePropagationTarget> getRenamePropagationTargets() {
return renamePropagationTargets;
}

public List<DeletePropagationTarget> getDeletePropagationTargets() {
return deletePropagationTargets;
}

public Map<String, String> getAutoComputeFormatPathByRefTypeNameMap() {
return autoComputeFormatPathByRefTypeNameMap;
}
Expand Down Expand Up @@ -403,6 +408,7 @@ void resolveReferencesPhase3(AtlasTypeRegistry typeRegistry) throws AtlasBaseExc

tagPropagationEdges.addAll(superType.tagPropagationEdges);
renamePropagationTargets.addAll(superType.renamePropagationTargets);
deletePropagationTargets.addAll(superType.deletePropagationTargets);
}

ownedRefAttributes = new ArrayList<>();
Expand Down Expand Up @@ -430,6 +436,7 @@ void resolveReferencesPhase3(AtlasTypeRegistry typeRegistry) throws AtlasBaseExc
ownedRefAttributes = Collections.unmodifiableList(ownedRefAttributes);
tagPropagationEdges = Collections.unmodifiableSet(tagPropagationEdges);
renamePropagationTargets = Collections.unmodifiableList(renamePropagationTargets);
deletePropagationTargets = Collections.unmodifiableList(deletePropagationTargets);

entityDef.setSubTypes(subTypes);

Expand Down Expand Up @@ -580,6 +587,7 @@ void resolveReferences(AtlasTypeRegistry typeRegistry) throws AtlasBaseException
this.businessAttributes = new HashMap<>(); // this will be populated in resolveReferences(), from AtlasBusinessMetadataType
this.tagPropagationEdges = new HashSet<>(); // this will be populated in resolveReferencesPhase2()
this.renamePropagationTargets = new ArrayList<>(); // this will be populated in resolveReferencesPhase2()
this.deletePropagationTargets = new ArrayList<>(); // this will be populated in resolveReferencesPhase2()
this.autoComputeFormatPathByRefTypeNameMap = new HashMap<>(); // this will be populated in resolveReferencesPhase3()

this.typeAndAllSubTypes.add(this.getTypeName());
Expand Down Expand Up @@ -891,6 +899,7 @@ void addRelationshipAttribute(String attributeName, AtlasAttribute attribute, At
}

addRenamePropagationTargetIfTriggered(relationshipType, attribute);
addDeletePropagationTargetIfTriggered(relationshipType, attribute);
}

/**
Expand Down Expand Up @@ -1122,6 +1131,54 @@ private void addRenamePropagationTargetIfTriggered(AtlasRelationshipType relatio
}
}

private void addDeletePropagationTargetIfTriggered(AtlasRelationshipType relationshipType, AtlasAttribute relationshipAttribute) {
AtlasRelationshipEndDef endDef1 = relationshipType.getRelationshipDef().getEndDef1();
AtlasRelationshipEndDef endDef2 = relationshipType.getRelationshipDef().getEndDef2();

if (endDef1 == null || endDef2 == null) {
LOG.debug("addDeletePropagationTargetIfTriggered({}): relationship type '{}' missing endDef — skipping",
getTypeName(), relationshipType.getTypeName());
return;
}

String thisTypeName = getTypeName();
boolean triggerOnEnd1 = StringUtils.equals(relationshipType.getEnd1Type().getTypeName(), thisTypeName) && endDef1.getIsPropagateDelete();
boolean triggerOnEnd2 = StringUtils.equals(relationshipType.getEnd2Type().getTypeName(), thisTypeName) && endDef2.getIsPropagateDelete();

if (!triggerOnEnd1 && !triggerOnEnd2) {
return;
}

String targetTypeName = triggerOnEnd1
? relationshipType.getEnd2Type().getTypeName()
: relationshipType.getEnd1Type().getTypeName();

RelationshipCategory category = relationshipType.getRelationshipDef().getRelationshipCategory();
DeletePropagationTarget target = new DeletePropagationTarget(targetTypeName, relationshipAttribute);

String relAttrName = relationshipAttribute.getAttributeDef() != null
? relationshipAttribute.getAttributeDef().getName()
: null;

if (!deletePropagationTargets.contains(target)) {
deletePropagationTargets.add(target);

LOG.info("addDeletePropagationTargetIfTriggered({}): delete propagation via relationship '{}' attribute '{}' -> target type '{}' (trigger {}, category {})",
thisTypeName,
relationshipType.getTypeName(),
relAttrName,
targetTypeName,
triggerOnEnd1 ? "end1" : "end2",
category);
} else {
LOG.debug("addDeletePropagationTargetIfTriggered({}): duplicate propagation target skipped — relationship '{}', attribute '{}', target type '{}'",
thisTypeName,
relationshipType.getTypeName(),
relAttrName,
targetTypeName);
}
}

boolean isAssignableFrom(AtlasObjectId objId) {
return AtlasTypeUtil.isValid(objId) && (StringUtils.equals(objId.getTypeName(), getTypeName()) || isSuperTypeOf(objId.getTypeName()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.atlas.type;

import java.util.Objects;

/**
* Typedef-time representation of a delete propagation target for an entity type.
*/
public class DeletePropagationTarget {
private final String targetTypeName;
private final AtlasStructType.AtlasAttribute relAttr;

public DeletePropagationTarget(String targetTypeName, AtlasStructType.AtlasAttribute relAttr) {
this.targetTypeName = targetTypeName;
this.relAttr = relAttr;
}

public String getTargetTypeName() {
return targetTypeName;
}

public AtlasStructType.AtlasAttribute getRelAttr() {
return relAttr;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DeletePropagationTarget that = (DeletePropagationTarget) o;
return Objects.equals(targetTypeName, that.targetTypeName)
&& relAttr == that.relAttr;
}

@Override
public int hashCode() {
return Objects.hash(targetTypeName, System.identityHashCode(relAttr));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,70 @@ public void testBooleanGettersSetters() {
endDef.setIsLegacyAttribute(false);
assertFalse(endDef.getIsLegacyAttribute());
}

@Test
public void testPropagateDeleteDefaultFalse() {
AtlasRelationshipEndDef endDef = new AtlasRelationshipEndDef();

assertFalse(endDef.getIsPropagateDelete());
}

@Test
public void testPropagateDeleteSetterGetter() {
AtlasRelationshipEndDef endDef = new AtlasRelationshipEndDef();

endDef.setIsPropagateDelete(true);
assertTrue(endDef.getIsPropagateDelete());

endDef.setIsPropagateDelete(false);
assertFalse(endDef.getIsPropagateDelete());
}

@Test
public void testPropagateDeleteCopyConstructor() {
AtlasRelationshipEndDef original = new AtlasRelationshipEndDef("Type1", "attr1", Cardinality.SINGLE);
original.setIsPropagateDelete(true);

AtlasRelationshipEndDef copy = new AtlasRelationshipEndDef(original);

assertTrue(copy.getIsPropagateDelete());
}

@Test
public void testPropagateDeleteEquality() {
AtlasRelationshipEndDef endDef1 = new AtlasRelationshipEndDef("Type1", "attr1", Cardinality.SINGLE);
AtlasRelationshipEndDef endDef2 = new AtlasRelationshipEndDef("Type1", "attr1", Cardinality.SINGLE);

assertEquals(endDef1, endDef2);

endDef1.setIsPropagateDelete(true);
assertNotEquals(endDef1, endDef2);

endDef2.setIsPropagateDelete(true);
assertEquals(endDef1, endDef2);
assertEquals(endDef1.hashCode(), endDef2.hashCode());
}

@Test
public void testPropagateDeleteSerialization() {
AtlasRelationshipEndDef original = new AtlasRelationshipEndDef("Database", "tables", Cardinality.SET, true);
original.setIsPropagateDelete(true);

String jsonString = AtlasType.toJson(original);
assertTrue(jsonString.contains("propagateDelete"));

AtlasRelationshipEndDef deserialized = AtlasType.fromJson(jsonString, AtlasRelationshipEndDef.class);
assertTrue(deserialized.getIsPropagateDelete());
assertEquals(deserialized, original);
}

@Test
public void testPropagateDeleteToString() {
AtlasRelationshipEndDef endDef = new AtlasRelationshipEndDef("Type1", "attr1", Cardinality.SINGLE);
endDef.setIsPropagateDelete(true);

String str = endDef.toString();
assertTrue(str.contains("propagateDelete"));
assertTrue(str.contains("true"));
}
}
Loading