This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch atlas-2.6
in repository https://gitbox.apache.org/repos/asf/atlas.git

commit fef1091877428edb0c380f16463c0f084fa34ff2
Author: bhor-sanket <[email protected]>
AuthorDate: Mon Aug 10 09:19:24 2026 +0530

    ATLAS-5312: Add delete propagation support via propagateDelete flag o… 
(#670)
---
 addons/models/6000-Trino/6000-trino_model.json     |   6 +-
 .../002-delete_propagation_typedef_patch.json      |  37 ++
 docs/src/documents/RenamePropagation.md            | 200 ---------
 docs/src/documents/TypeSystem.md                   | 155 +++++++
 .../model/typedef/AtlasRelationshipEndDef.java     |  26 +-
 .../org/apache/atlas/type/AtlasEntityType.java     |  57 +++
 .../apache/atlas/type/DeletePropagationTarget.java |  59 +++
 .../model/typedef/TestAtlasRelationshipEndDef.java |  66 +++
 .../atlas/type/TestDeletePropagationTarget.java    | 122 ++++++
 .../bootstrap/AtlasTypeDefStoreInitializer.java    |  61 ++-
 .../repository/store/graph/v1/DeleteHandlerV1.java |  40 ++
 .../graph/v2/EntityDeletePropagationHandler.java   | 133 ++++++
 .../AtlasTypeDefStoreInitializerTest.java          | 234 ++++++++--
 .../v2/EntityDeletePropagationHandlerTest.java     | 476 +++++++++++++++++++++
 14 files changed, 1419 insertions(+), 253 deletions(-)

diff --git a/addons/models/6000-Trino/6000-trino_model.json 
b/addons/models/6000-Trino/6000-trino_model.json
index aa254ee6ed..a8aa885de0 100644
--- a/addons/models/6000-Trino/6000-trino_model.json
+++ b/addons/models/6000-Trino/6000-trino_model.json
@@ -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",
@@ -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" }
     },
     {
@@ -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" }
     },
     {
diff --git 
a/addons/models/6000-Trino/patches/002-delete_propagation_typedef_patch.json 
b/addons/models/6000-Trino/patches/002-delete_propagation_typedef_patch.json
new file mode 100644
index 0000000000..21b95d5d47
--- /dev/null
+++ b/addons/models/6000-Trino/patches/002-delete_propagation_typedef_patch.json
@@ -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"
+      }
+    }
+  ]
+}
diff --git a/docs/src/documents/RenamePropagation.md 
b/docs/src/documents/RenamePropagation.md
deleted file mode 100644
index b261c2adfe..0000000000
--- a/docs/src/documents/RenamePropagation.md
+++ /dev/null
@@ -1,200 +0,0 @@
----
-
-## name: Rename Propagation
-
-route: /RenamePropagation
-menu: Documentation
-submenu: Features
-
-# Rename propagation
-
-**Rename propagation** is an Atlas feature for **partial entity updates**: 
when a renamed instance’s `**name`** (or a mapped source attribute) changes, 
the server can find **related entities** whose **unique attribute** (for most 
types, `**qualifiedName`**) still embeds the old segment, recompute that unique 
attribute from the type’s `**autoComputeFormat`**, and **persist those updates 
in the same transaction** as the triggering entity. Discovery is driven by 
**typedefs** (relationship e [...]
-
----
-
-## Why it exists
-
-The unique attribute (qualified name) is often a **composite string** built 
from ancestor names (database, table, cluster, and so on). If only the root 
entity is updated in a hook payload, **downstream** instances can keep an 
outdated unique attribute until something rewrites them. Rename propagation 
closes that gap by following **marked relationship edges** from the updated 
vertex and applying the same **parse → replace one template segment → rebuild** 
logic per dependent type.
-
----
-
-## Example: Hive database rename
-
-Assume a Hive model where:
-
-- `**hive_db`** has unique attribute (qualified name) shaped like 
`{name}@{clusterName}` (for example `sales@cm`).
-- `**hive_table`** uses `{db.name}.{name}@{clusterName}` (for example 
`sales.orders@cm`).
-- `**hive_column**` uses `{table.db.name}.{table.name}.{name}@{clusterName}`.
-
-The `**hive_table_db**` relationship links each table to its database; 
`**hive_table_columns**` links columns to a table. Relationship ends that opt 
in with `**propagateRename**` tell Atlas: when the **trigger** side is renamed, 
walk to neighbors and refresh their unique attribute (qualified name) where the 
template depends on that rename.
-
-1. An integration renames the database `**sales`** → `**sales_archive`** 
(partial update; `**name**` and thus the unique attribute on the `hive_db` 
vertex change).
-2. Atlas detects that the entity’s **unique attribute (qualified name)** 
changed and that `**hive_db`** has **rename propagation targets** from typedef 
resolution.
-3. `**EntityRenameHandler`** follows the configured relationship from the 
database vertex to related `**hive_table`** vertices, recomputes each table’s 
string (for example `sales.orders@cm` → `sales_archive.orders@cm`), and 
registers those rows on the mutation context.
-4. If `**hive_table**` also declares propagation targets, the same process 
continues to `**hive_column**` rows (for example `sales.orders.col1@cm` → 
`sales_archive.orders.col1@cm`).
-
-All of this happens **without** requiring the hook to send every table and 
column in one batch.
-
----
-
-## Example: Hive table → Trino table → Trino column
-
-Assume a Hive and Trino model where:
-
-- `**hive_table**` has unique attribute (qualified name) shaped like 
`{db.name}.{name}@{clusterName}` (for example `default.orders@cluster`).
-- `**trino_table**` uses 
`{trinoschema.catalog.name}.{trinoschema.name}.{name}@{trinoschema.catalog.instance.name}`
 (for example `cat1.schema1.orders@inst1`).
-- `**trino_column**` uses 
`{table.trinoschema.catalog.name}.{table.trinoschema.name}.{table.name}.{name}@{table.trinoschema.catalog.instance.name}`
 (for example `cat1.schema1.orders.col1@inst1`).
-
-The `**trino_table_hive_table**` relationship links each Trino table to its 
Hive table; `**trino_table_columns**` links columns to a table. The 
`**propagateRename**` flag on the `**hive_table**` end of 
`**trino_table_hive_table**` tells Atlas: when the **trigger** Hive table is 
renamed, walk to linked `**trino_table**` vertices and refresh their unique 
attribute (qualified name) where the `**autoComputeFormat**` template depends 
on that rename.
-
-1. An integration renames the Hive table `**orders**` → `**orders_v2**` 
(partial update; `**name**` and thus the unique attribute on the 
`**hive_table**` vertex change).
-2. Atlas detects that the entity's **unique attribute (qualified name)** 
changed and that `**hive_table**` has **rename propagation targets** from 
typedef resolution.
-3. `**EntityRenameHandler**` follows `**trino_table_hive_table**` from the 
Hive table vertex to related `**trino_table**` vertices, recomputes each 
table's string (for example `cat1.schema1.orders@inst1` → 
`cat1.schema1.orders_v2@inst1`), and registers those rows on the mutation 
context.
-4. If `**trino_table**` also declares propagation targets, the same process 
continues to `**trino_column**` rows (for example 
`cat1.schema1.orders.col1@inst1` → `cat1.schema1.orders_v2.col1@inst1`).
-
-All of this happens **without** requiring the hook to send every Trino table 
and column in one batch.
-
----
-
-## How it works (two stages)
-
-### 1. Typedef resolution (startup / type updates)
-
-While the type system resolves references, Atlas builds **per-entity-type** 
metadata used later at runtime:
-
-- `**propagateRename`** on a `**AtlasRelationshipEndDef`** marks which end 
acts as the **rename source** for that relationship so instances reached 
through that edge can be updated when the source side’s name changes.
-- `**attributeDefOverrides`** on `**AtlasEntityDef`** can set 
`**autoComputeFormat**` for the **unique attribute** (qualified name), 
describing how the string is composed from named segments.
-- For paths that do not use explicit `**propagateAttributes`** maps on the 
relationship end, `**AtlasEntityType`** also maintains 
`**autoComputeFormatPathByRefTypeNameMap**`: referenced type name → **dotted 
path** in the template for the segment to replace when that referenced type was 
renamed.
-- Optional `**propagateAttributes`** on a relationship end lists `**source`** 
/ `**target**` attribute pairs so values such as `**name**` can be written to 
the right stub fields on dependents when models need more than template 
substitution alone.
-
-Together, these produce a list of `**RenamePropagationTarget**` entries on 
each `**AtlasEntityType**`.
-
-### 2. Entity update (runtime)
-
-On **partial update**, `**AtlasEntityStoreV2`** compares the stored **unique 
attribute (qualified name)** on the graph vertex to the incoming value. If it 
changed and the type has **rename propagation targets**, 
`**EntityRenameHandler`** walks the graph along those relationships, recomputes 
each dependent’s unique attribute from its effective `**autoComputeFormat`**, 
and adds minimal **dependent stubs** (guid, new unique attribute, optional 
mapped attributes) to the **same** mutation con [...]
-
----
-
-## Key typedef concepts
-
-
-| Concept                                     | Where it lives            | 
Role                                                                            
                                                                                
                                     |
-| ------------------------------------------- | ------------------------- | 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 |
-| `**attributeDefOverrides`**                 | `AtlasEntityDef`          | 
Supplies per-type overrides such as `**autoComputeFormat`** for the unique 
attribute (qualified name); merged during hierarchy resolution and stored on 
the entity type vertex when supported.       |
-| `**propagateRename**`                       | `AtlasRelationshipEndDef` | 
Marks the end whose entity type is the **trigger** for propagation across that 
relationship.                                                                   
                                      |
-| `**propagateAttributes`**                   | `AtlasRelationshipEndDef` | 
Optional maps from a **source** attribute on the trigger side to **target** 
attribute names on the dependent entity.                                        
                                         |
-| `**RenamePropagationTarget**`               | `AtlasEntityType`         | 
Precomputed link: target type name, relationship attribute for edge traversal, 
and propagate-attribute list.                                                   
                                      |
-| `**autoComputeFormatPathByRefTypeNameMap`** | `AtlasEntityType`         | 
Referenced entity type name → dotted path in the dependent’s 
**autoComputeFormat** for the segment to rewrite when that type was renamed 
(when `**propagateAttributes`** is not used for that path). |
-
-
----
-
-## Implementation reference
-
-
-| Component                                                | Role              
                                                                                
                                                                 |
-| -------------------------------------------------------- | 
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 |
-| `**AtlasEntityType`**                                    | Registers rename 
propagation targets and builds `**autoComputeFormatPathByRefTypeNameMap**` 
during reference resolution.                                           |
-| `**EntityRenameHandler**`                                | Traverses edges, 
parses/rebuilds the unique attribute string, registers `**DependentUpdate**` 
entries on the mutation context.                                     |
-| `**AtlasEntityStoreV2**`                                 | Detects a changed 
unique attribute (qualified name) on partial update and invokes the handler.    
                                                                 |
-| `**AtlasEntityDefStoreV2` / `AtlasTypeDefGraphStoreV2**` | Persist and read 
`**attributeDefOverrides**` via the type vertex property keyed by 
`**TYPE_ATTR_DEF_OVERRIDES_PROPERTY_KEY**`.                                     
|
-| `**AtlasTypeDefStoreInitializer**`                       | Applies typedef 
patches such as `**SET_ATTRIBUTE_DEF_OVERRIDES**` and 
`**SET_PROPAGATE_RENAME**` (patch params use `**endDefToken**`: `"endDef1"` or 
`"endDef2"`). |
-| `**GraphBackedSearchIndexer**`                           | Indexes the 
overrides property for search where applicable.                                 
                                                                       |
-
-
----
-
-## Models and patches
-
-- **Bootstrap model JSON** — Declare `**attributeDefOverrides`** for the 
unique attribute (qualified name) and `**propagateRename`** / 
`**propagateAttributes**` on relationship ends as required by your connectors 
(for example Hive, Trino).
-- **Patch files** — Ship one-off typedef updates under 
`**addons/.../patches/`** so each patch runs once and is tracked (for example 
`**AtlasPatchRegistry`**), avoiding reliance on version-only bumps on every 
restart.
-
----
-
-## Configuring `autoComputeFormat` on an entity type
-
-Rename propagation recomputes a dependent’s **unique attribute** (usually 
`**qualifiedName**`) by **parsing** the stored string against the type’s 
`**autoComputeFormat**`, **replacing** the segment that came from the renamed 
entity, and **rebuilding** the string. You supply that template on the **entity 
typedef** as an override on the unique attribute, for **any** entity type whose 
composite id should stay aligned after upstream renames.
-
-### Where to declare it
-
-In **bootstrap model JSON** add an `**attributeDefOverrides`** array on the 
`**entityDefs**` entry. Each override targets an attribute by name; set 
`**autoComputeFormat**` on the **unique** attribute (conventionally 
`**qualifiedName`**):
-
-```json
-{
-  "name": "my_column",
-  "superTypes": ["DataSet"],
-  "attributeDefOverrides": [
-    {
-      "name": "qualifiedName",
-      "autoComputeFormat": "{table.db.name}.{table.name}.{name}@{clusterName}"
-    }
-  ],
-  "attributeDefs": [ ]
-}
-```
-
-Atlas merges overrides with inherited definitions so the resolved 
`**AtlasEntityDef**` carries `**autoComputeFormat**` on `**qualifiedName**`.
-
-### Placeholder syntax
-
-- Use **curly braces** for each slot: `{segmentKey}`.
-- **Literals** between placeholders (for example `.`, `/`, `@`) are 
**delimiters**. When parsing an existing value, the character **immediately 
after** each `}` in the format marks where that slot ends in the stored string 
(the **last** placeholder consumes the rest of the string). Pick delimiters 
that do not appear inside real slot values, or parsing becomes ambiguous.
-- **Dots inside a key** are part of the key string only (they are not JSON 
nesting in the model file).
-
-### How to design the format for a new entity type
-
-Start from **one real qualified-name string** your connector already produces 
(for example `sales.orders@prod`). Notice how it is built from smaller parts 
and which **fixed characters** separate those parts (here: `.`, `.`, and `@`).
-
-1. Write the **same separators in the same order** in `**autoComputeFormat**`.
-2. Replace each **variable part** with `{...}`:
-   - **`{name}`** — this entity’s own **name** (always a single word inside 
the braces).
-   - **`{db.name}`**, **`{table.db.name}`**, and similar — each segment 
**before** the final `.name` is one **relationship hop** (the relationship 
attribute name on the typedef at that step). Together they lead to a related 
entity; the trailing `.name` means “use that entity’s **name** attribute.”
-   - **`{clusterName}`** (one word inside the braces) — a normal attribute on 
**this** same entity, not a relationship hop.
-
-Dots **inside** `{...}` are part of the placeholder key, not nested JSON in 
the model file. Choose separators that will not appear inside a single part of 
the real string, so parsing stays unambiguous.
-
-### Example: table under a database (Hive-style shape)
-
-If `**my_table**` links to a database via relationship attribute `**db**` and 
carries a cluster label `**clusterName**`, a common pattern is:
-
-```json
-"attributeDefOverrides": [
-  {
-    "name": "qualifiedName",
-    "autoComputeFormat": "{db.name}.{name}@{clusterName}"
-  }
-]
-```
-
-- `{db.name}` — database’s `**name**` reached through `**db**`.
-- `{name}` — this table’s `**name**`.
-- `@{clusterName}` — literal `@` then this entity’s `**clusterName**` 
attribute.
-
-### Example: deeper chain (Trino column)
-
-For `**trino_column**`, the unique string chains catalog, schema, table, 
column, and instance:
-
-```json
-"attributeDefOverrides": [
-  {
-    "name": "qualifiedName",
-    "autoComputeFormat": 
"{table.trinoschema.catalog.name}.{table.trinoschema.name}.{table.name}.{name}@{table.trinoschema.catalog.instance.name}"
-  }
-]
-```
-
-Meaning of each slot:
-
-- `{table.trinoschema.catalog.name}` — from this column, follow `**table**` → 
`**trinoschema**` → `**catalog**`, then use that catalog entity’s `**name**`.
-- `{table.trinoschema.name}` — schema `**name**` along the same branch.
-- `{table.name}` — table `**name**`.
-- `{name}` — this column’s own `**name**`.
-- `@{table.trinoschema.catalog.instance.name}` — after `@`, follow 
`**instance**` from the catalog and use that instance’s `**name**`.
-
----
-
-## Tests
-
-- `**AtlasTypeDefStoreInitializerTest**` — Patch handlers for overrides and 
`**SET_PROPAGATE_RENAME**`.
-- `**EntityRenameHandlerTest**` — End-to-end behavior: unique attribute 
recompute, mapped attributes, and multi-hop propagation through typedefs.
-
diff --git a/docs/src/documents/TypeSystem.md b/docs/src/documents/TypeSystem.md
index 7cfbc70d16..0c5dad69b4 100644
--- a/docs/src/documents/TypeSystem.md
+++ b/docs/src/documents/TypeSystem.md
@@ -204,3 +204,158 @@ example, an ETL process that transforms a hive table with 
raw data to another hi
 be a specific type that extends the Process type. A Process type has two 
specific attributes, inputs and outputs. Both
 inputs and outputs are arrays of DataSet entities. Thus, an instance of a 
Process type can use these inputs and outputs
 to capture how the lineage of a DataSet evolves.
+
+## Relationship propagation model extensions
+
+Atlas supports automatic propagation of rename and delete operations through 
relationships. This is driven by fields on
+**entity type definitions** and **relationship end definitions** in the model 
JSON.
+
+### Entity definition: attributeDefOverrides
+
+An entity type can declare `attributeDefOverrides` to specify how its unique 
attribute (qualifiedName) is composed from
+related entities. The `autoComputeFormat` template tells Atlas how to parse 
and rebuild the qualifiedName when an
+upstream entity is renamed.
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"entityDefs": [{
+    "name": "trino_table",
+    "superTypes": ["DataSet"],
+    "attributeDefOverrides": [
+        {
+            "name": "qualifiedName",
+            "autoComputeFormat": 
"{trinoschema.catalog.name}.{trinoschema.name}.{name}@{trinoschema.catalog.instance.name}"
+        }
+    ]
+}]`}
+</SyntaxHighlighter>
+
+Each `{placeholder}` maps to an attribute path: `{name}` refers to this 
entity's own name, while dotted paths like
+`{trinoschema.catalog.name}` traverse relationship attributes to reach a 
related entity's name.
+
+#### Placeholder syntax
+
+- Use **curly braces** for each slot: `{segmentKey}`.
+- **Literals** between placeholders (e.g. `.`, `/`, `@`) act as 
**delimiters**. When parsing an existing qualifiedName,
+  the character immediately after each `}` marks where that slot ends in the 
stored string. The **last** placeholder
+  consumes the rest of the string. Choose delimiters that do not appear inside 
real slot values to avoid ambiguity.
+- **Dots inside a key** (e.g. `{table.db.name}`) are part of the key string — 
they represent relationship hops, not
+  JSON nesting.
+
+#### How to design autoComputeFormat for a new entity type
+
+Start from **one real qualifiedName string** your connector already produces 
(e.g. `sales.orders@prod`). Notice how it
+is built from smaller parts and which **fixed characters** separate those 
parts (here `.` and `@`).
+
+1. Write the **same separators in the same order** in `autoComputeFormat`.
+2. Replace each **variable part** with a `{placeholder}`:
+   - **`{name}`** — this entity's own `name` attribute.
+   - **`{db.name}`**, **`{table.db.name}`**, etc. — each segment before the 
final `.name` is one **relationship hop**
+     (the relationship attribute name on the typedef at that step). Together 
they navigate to a related entity; the
+     trailing `.name` means "use that entity's `name` attribute."
+   - **`{clusterName}`** (single word, no dots) — a plain attribute on 
**this** entity, not a relationship hop.
+
+##### Example: table under a database (Hive-style)
+
+If `my_table` links to a database via relationship attribute `db` and carries 
a cluster label `clusterName`:
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"attributeDefOverrides": [
+    {
+        "name": "qualifiedName",
+        "autoComputeFormat": "{db.name}.{name}@{clusterName}"
+    }
+]`}
+</SyntaxHighlighter>
+
+- `{db.name}` — the database's `name`, reached through relationship attribute 
`db`.
+- `{name}` — this table's own `name`.
+- `@{clusterName}` — literal `@` followed by this entity's `clusterName` 
attribute.
+
+##### Example: deeper chain (Trino column)
+
+For `trino_column`, the qualifiedName chains catalog, schema, table, column, 
and instance:
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"attributeDefOverrides": [
+    {
+        "name": "qualifiedName",
+        "autoComputeFormat": 
"{table.trinoschema.catalog.name}.{table.trinoschema.name}.{table.name}.{name}@{table.trinoschema.catalog.instance.name}"
+    }
+]`}
+</SyntaxHighlighter>
+
+- `{table.trinoschema.catalog.name}` — from this column, follow `table` → 
`trinoschema` → `catalog`, use that entity's `name`.
+- `{table.trinoschema.name}` — schema `name` along the same path.
+- `{table.name}` — the parent table's `name`.
+- `{name}` — this column's own `name`.
+- `@{table.trinoschema.catalog.instance.name}` — after `@`, follow `instance` 
from the catalog and use its `name`.
+
+
+### Relationship end definition: propagateRename
+
+Setting `propagateRename: true` on a relationship end marks that end as the 
**trigger** for rename propagation. When an
+entity on that end is renamed, Atlas traverses the relationship to dependent 
entities and recomputes their qualifiedName
+using their `autoComputeFormat`.
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"relationshipDefs": [{
+    "name": "trino_table_hive_table",
+    "endDef1": {
+        "type": "hive_table",
+        "name": "trinoTables",
+        "propagateRename": true
+    },
+    "endDef2": {
+        "type": "trino_table",
+        "name": "hiveTable"
+    }
+}]`}
+</SyntaxHighlighter>
+
+An optional `propagateAttributes` list on the same end can map source 
attributes to target attributes for cases where
+simple template substitution is not sufficient:
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"endDef1": {
+    "type": "hive_table",
+    "name": "trinoTables",
+    "propagateRename": true,
+    "propagateAttributes": [
+        { "source": "name", "target": "hiveTableName" }
+    ]
+}`}
+</SyntaxHighlighter>
+
+### Relationship end definition: propagateDelete
+
+Setting `propagateDelete: true` on a relationship end marks that end as the 
**trigger** for delete propagation. When an
+entity on that end is deleted, Atlas propagates the deletion to dependent 
entities reached through the relationship.
+
+<SyntaxHighlighter wrapLines={true} language="json" style={theme.dark}>
+{`"relationshipDefs": [{
+    "name": "trino_table_hive_table",
+    "endDef1": {
+        "type": "hive_table",
+        "name": "trinoTables",
+        "propagateRename": true,
+        "propagateDelete": true
+    },
+    "endDef2": {
+        "type": "trino_table",
+        "name": "hiveTable"
+    }
+}]`}
+</SyntaxHighlighter>
+
+In this example, deleting a `hive_table` entity will automatically propagate 
deletion to any linked `trino_table`
+entities. If those targets also have delete propagation configured on their 
own relationships (e.g.,
+`trino_table_columns`), the propagation continues recursively.
+
+### Summary of propagation fields
+
+| Field | Location | Purpose |
+|-------|----------|---------|
+| `attributeDefOverrides.autoComputeFormat` | Entity definition | Template for 
computing qualifiedName from related entities |
+| `propagateRename` | Relationship end definition | Triggers qualifiedName 
recomputation on dependents when this end is renamed |
+| `propagateAttributes` | Relationship end definition | Maps source attributes 
to target attributes during rename |
+| `propagateDelete` | Relationship end definition | Triggers propagation of 
deletion to dependents when this end is deleted |
\ No newline at end of file
diff --git 
a/intg/src/main/java/org/apache/atlas/model/typedef/AtlasRelationshipEndDef.java
 
b/intg/src/main/java/org/apache/atlas/model/typedef/AtlasRelationshipEndDef.java
index c7921b6ca3..568b7535ff 100644
--- 
a/intg/src/main/java/org/apache/atlas/model/typedef/AtlasRelationshipEndDef.java
+++ 
b/intg/src/main/java/org/apache/atlas/model/typedef/AtlasRelationshipEndDef.java
@@ -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
@@ -177,6 +186,7 @@ public class AtlasRelationshipEndDef implements 
Serializable {
             setDescription(other.description);
             setIsPropagateRename(other.propagateRename);
             setPropagateAttributes(other.propagateAttributes);
+            setIsPropagateDelete(other.propagateDelete);
         }
     }
 
@@ -254,6 +264,7 @@ public class AtlasRelationshipEndDef implements 
Serializable {
         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;
@@ -261,7 +272,7 @@ public class AtlasRelationshipEndDef implements 
Serializable {
 
     @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
@@ -281,7 +292,8 @@ public class AtlasRelationshipEndDef implements 
Serializable {
                 cardinality == that.cardinality &&
                 isLegacyAttribute == that.isLegacyAttribute &&
                 propagateRename == that.propagateRename &&
-                Objects.equals(propagateAttributes, that.propagateAttributes);
+                Objects.equals(propagateAttributes, that.propagateAttributes) 
&&
+                propagateDelete == that.propagateDelete;
     }
 
     @JsonProperty("propagateRename")
@@ -302,6 +314,16 @@ public class AtlasRelationshipEndDef implements 
Serializable {
         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();
diff --git a/intg/src/main/java/org/apache/atlas/type/AtlasEntityType.java 
b/intg/src/main/java/org/apache/atlas/type/AtlasEntityType.java
index 9309d1c254..922aada3d4 100644
--- a/intg/src/main/java/org/apache/atlas/type/AtlasEntityType.java
+++ b/intg/src/main/java/org/apache/atlas/type/AtlasEntityType.java
@@ -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
@@ -267,6 +268,10 @@ public class AtlasEntityType extends AtlasStructType {
         return renamePropagationTargets;
     }
 
+    public List<DeletePropagationTarget> getDeletePropagationTargets() {
+        return deletePropagationTargets;
+    }
+
     public Map<String, String> getAutoComputeFormatPathByRefTypeNameMap() {
         return autoComputeFormatPathByRefTypeNameMap;
     }
@@ -403,6 +408,7 @@ public class AtlasEntityType extends AtlasStructType {
 
             tagPropagationEdges.addAll(superType.tagPropagationEdges);
             
renamePropagationTargets.addAll(superType.renamePropagationTargets);
+            
deletePropagationTargets.addAll(superType.deletePropagationTargets);
         }
 
         ownedRefAttributes = new ArrayList<>();
@@ -430,6 +436,7 @@ public class AtlasEntityType extends AtlasStructType {
         ownedRefAttributes       = 
Collections.unmodifiableList(ownedRefAttributes);
         tagPropagationEdges      = 
Collections.unmodifiableSet(tagPropagationEdges);
         renamePropagationTargets = 
Collections.unmodifiableList(renamePropagationTargets);
+        deletePropagationTargets = 
Collections.unmodifiableList(deletePropagationTargets);
 
         entityDef.setSubTypes(subTypes);
 
@@ -580,6 +587,7 @@ public class AtlasEntityType extends AtlasStructType {
         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());
@@ -891,6 +899,7 @@ public class AtlasEntityType extends AtlasStructType {
         }
 
         addRenamePropagationTargetIfTriggered(relationshipType, attribute);
+        addDeletePropagationTargetIfTriggered(relationshipType, attribute);
     }
 
     /**
@@ -1122,6 +1131,54 @@ public class AtlasEntityType extends AtlasStructType {
         }
     }
 
+    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()));
     }
diff --git 
a/intg/src/main/java/org/apache/atlas/type/DeletePropagationTarget.java 
b/intg/src/main/java/org/apache/atlas/type/DeletePropagationTarget.java
new file mode 100644
index 0000000000..66d2674501
--- /dev/null
+++ b/intg/src/main/java/org/apache/atlas/type/DeletePropagationTarget.java
@@ -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));
+    }
+}
diff --git 
a/intg/src/test/java/org/apache/atlas/model/typedef/TestAtlasRelationshipEndDef.java
 
b/intg/src/test/java/org/apache/atlas/model/typedef/TestAtlasRelationshipEndDef.java
index 1185275019..1eabbcf848 100644
--- 
a/intg/src/test/java/org/apache/atlas/model/typedef/TestAtlasRelationshipEndDef.java
+++ 
b/intg/src/test/java/org/apache/atlas/model/typedef/TestAtlasRelationshipEndDef.java
@@ -363,4 +363,70 @@ public class TestAtlasRelationshipEndDef {
         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"));
+    }
 }
diff --git 
a/intg/src/test/java/org/apache/atlas/type/TestDeletePropagationTarget.java 
b/intg/src/test/java/org/apache/atlas/type/TestDeletePropagationTarget.java
new file mode 100644
index 0000000000..6a3120d24f
--- /dev/null
+++ b/intg/src/test/java/org/apache/atlas/type/TestDeletePropagationTarget.java
@@ -0,0 +1,122 @@
+/**
+ * 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 org.apache.atlas.type.AtlasStructType.AtlasAttribute;
+import org.testng.annotations.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestDeletePropagationTarget {
+    @Test
+    public void testConstructorAndGetters() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+
+        DeletePropagationTarget target = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        assertEquals(target.getTargetTypeName(), "trino_table");
+        assertEquals(target.getRelAttr(), relAttr);
+    }
+
+    @Test
+    public void testConstructorWithNullValues() {
+        DeletePropagationTarget target = new DeletePropagationTarget(null, 
null);
+
+        assertNull(target.getTargetTypeName());
+        assertNull(target.getRelAttr());
+    }
+
+    @Test
+    public void testEqualsSameInstance() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        assertTrue(target.equals(target));
+    }
+
+    @Test
+    public void testEqualsSameValues() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target1 = new 
DeletePropagationTarget("trino_table", relAttr);
+        DeletePropagationTarget target2 = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        assertEquals(target1, target2);
+        assertEquals(target1.hashCode(), target2.hashCode());
+    }
+
+    @Test
+    public void testEqualsIdentityOnRelAttr() {
+        AtlasAttribute relAttr1 = mock(AtlasAttribute.class);
+        AtlasAttribute relAttr2 = mock(AtlasAttribute.class);
+        DeletePropagationTarget target1 = new 
DeletePropagationTarget("trino_table", relAttr1);
+        DeletePropagationTarget target2 = new 
DeletePropagationTarget("trino_table", relAttr2);
+
+        assertNotEquals(target1, target2);
+    }
+
+    @Test
+    public void testEqualsDifferentTypeName() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target1 = new 
DeletePropagationTarget("trino_table", relAttr);
+        DeletePropagationTarget target2 = new 
DeletePropagationTarget("trino_schema", relAttr);
+
+        assertNotEquals(target1, target2);
+    }
+
+    @Test
+    public void testEqualsNull() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        assertFalse(target.equals(null));
+    }
+
+    @Test
+    public void testEqualsDifferentClass() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        assertFalse(target.equals("not a target"));
+    }
+
+    @Test
+    public void testHashCodeConsistency() {
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        DeletePropagationTarget target = new 
DeletePropagationTarget("trino_table", relAttr);
+
+        int hash1 = target.hashCode();
+        int hash2 = target.hashCode();
+
+        assertEquals(hash1, hash2);
+    }
+
+    @Test
+    public void testHashCodeDifferentForDifferentTargets() {
+        AtlasAttribute relAttr1 = mock(AtlasAttribute.class);
+        AtlasAttribute relAttr2 = mock(AtlasAttribute.class);
+        DeletePropagationTarget target1 = new 
DeletePropagationTarget("trino_table", relAttr1);
+        DeletePropagationTarget target2 = new 
DeletePropagationTarget("trino_schema", relAttr2);
+
+        assertNotEquals(target1.hashCode(), target2.hashCode());
+    }
+}
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializer.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializer.java
index b7483f4ba8..31e748096c 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializer.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializer.java
@@ -465,7 +465,7 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
                     new RemoveLegacyRefAttributesPatchHandler(typeDefStore, 
typeRegistry),
                     new UpdateTypeDefOptionsPatchHandler(typeDefStore, 
typeRegistry),
                     new SetServiceTypePatchHandler(typeDefStore, typeRegistry),
-                    new 
AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore, typeRegistry),
+                    new 
AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore, 
typeRegistry),
                     new UpdateAttributeMetadataHandler(typeDefStore, 
typeRegistry, graph),
                     new AddSuperTypePatchHandler(typeDefStore, typeRegistry),
                     new AddMandatoryAttributePatchHandler(typeDefStore, 
typeRegistry)
@@ -1184,19 +1184,20 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
     }
 
     /**
-     * Handles {@code SET_ATTRIBUTE_DEF_OVERRIDES} on entity typedefs and 
{@code SET_PROPAGATE_RENAME} on relationship typedef end defs.
+     * Handles {@code SET_ATTRIBUTE_DEF_OVERRIDES} on entity typedefs, {@code 
SET_PROPAGATE_RENAME} and {@code SET_PROPAGATE_DELETE} on relationship typedef 
end defs.
      * Optional {@code params.propagateAttributes} for {@code 
SET_PROPAGATE_RENAME}: a JSON array of objects, each copied to a {@code 
HashMap<String,String>} on the patched end.
      */
-    static class AttributeDefOverridesAndPropagateRenamePatchHandler extends 
PatchHandler {
+    static class AttributeDefOverridesAndOperationPropagationPatchHandler 
extends PatchHandler {
         private static final String ACTION_SET_ATTRIBUTE_DEF_OVERRIDES = 
"SET_ATTRIBUTE_DEF_OVERRIDES";
         private static final String ACTION_SET_PROPAGATE_RENAME        = 
"SET_PROPAGATE_RENAME";
+        private static final String ACTION_SET_PROPAGATE_DELETE        = 
"SET_PROPAGATE_DELETE";
         /** Patch JSON key under {@code params}: value is {@code "endDef1"} or 
{@code "endDef2"} (string, not numeric). */
         private static final String PARAM_END_DEF_TOKEN                = 
"endDefToken";
         /** Optional: JSON array of objects under {@code params}; each object 
becomes one {@code Map<String,String>}. */
         private static final String PARAM_PROPAGATE_ATTRIBUTES         = 
"propagateAttributes";
 
-        public 
AttributeDefOverridesAndPropagateRenamePatchHandler(AtlasTypeDefStore 
typeDefStore, AtlasTypeRegistry typeRegistry) {
-            super(typeDefStore, typeRegistry, new String[] 
{ACTION_SET_ATTRIBUTE_DEF_OVERRIDES, ACTION_SET_PROPAGATE_RENAME});
+        public 
AttributeDefOverridesAndOperationPropagationPatchHandler(AtlasTypeDefStore 
typeDefStore, AtlasTypeRegistry typeRegistry) {
+            super(typeDefStore, typeRegistry, new String[] 
{ACTION_SET_ATTRIBUTE_DEF_OVERRIDES, ACTION_SET_PROPAGATE_RENAME, 
ACTION_SET_PROPAGATE_DELETE});
         }
 
         @Override
@@ -1215,7 +1216,7 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
                 return SKIPPED;
             }
 
-            
LOG.debug("AttributeDefOverridesAndPropagateRenamePatchHandler.applyPatch(): 
id={}; action={}; typeName={}",
+            
LOG.debug("AttributeDefOverridesAndOperationPropagationPatchHandler.applyPatch():
 id={}; action={}; typeName={}",
                     patch.getId(), patch.getAction(), typeName);
 
             String action = patch.getAction();
@@ -1234,6 +1235,13 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
                 }
 
                 return applyPropagateRename(patch, (AtlasRelationshipDef) 
typeDef);
+            } else if (ACTION_SET_PROPAGATE_DELETE.equals(action)) {
+                if (!(typeDef instanceof AtlasRelationshipDef)) {
+                    throw new 
AtlasBaseException(AtlasErrorCode.PATCH_NOT_APPLICABLE_FOR_TYPE, 
patch.getAction(),
+                            typeDef.getClass().getSimpleName());
+                }
+
+                return applyPropagateDelete(patch, (AtlasRelationshipDef) 
typeDef);
             }
 
             throw new AtlasBaseException(AtlasErrorCode.PATCH_INVALID_DATA, 
patch.getAction(), typeName);
@@ -1247,7 +1255,7 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
 
             int overrideCount = 
CollectionUtils.isEmpty(patch.getAttributeDefs()) ? 0 : 
patch.getAttributeDefs().size();
 
-            
LOG.debug("AttributeDefOverridesAndPropagateRenamePatchHandler.applyEntityDefOverrides():
 entityType={}; overrideCount={}; updateToVersion={}",
+            
LOG.debug("AttributeDefOverridesAndOperationPropagationPatchHandler.applyEntityDefOverrides():
 entityType={}; overrideCount={}; updateToVersion={}",
                     typeDef.getName(), overrideCount, 
patch.getUpdateToVersion());
 
             typeDefStore.updateEntityDefByName(typeDef.getName(), updatedDef);
@@ -1290,7 +1298,7 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
 
             updatedDef.setTypeVersion(patch.getUpdateToVersion());
 
-            
LOG.debug("AttributeDefOverridesAndPropagateRenamePatchHandler.applyPropagateRename():
 relationshipType={}; endDefToken={}; updateToVersion={}",
+            
LOG.debug("AttributeDefOverridesAndOperationPropagationPatchHandler.applyPropagateRename():
 relationshipType={}; endDefToken={}; updateToVersion={}",
                     typeDef.getName(), endDefToken, 
patch.getUpdateToVersion());
 
             typeDefStore.updateRelationshipDefByName(typeDef.getName(), 
updatedDef);
@@ -1301,6 +1309,43 @@ public class AtlasTypeDefStoreInitializer implements 
ActiveStateChangeHandler {
             return APPLIED;
         }
 
+        private PatchStatus applyPropagateDelete(TypeDefPatch patch, 
AtlasRelationshipDef typeDef) throws AtlasBaseException {
+            Object endDefObj = patch.getParams() != null ? 
patch.getParams().get(PARAM_END_DEF_TOKEN) : null;
+            String endDefToken = endDefObj != null ? 
String.valueOf(endDefObj).trim() : null;
+            boolean useEndDef2 = "endDef2".equalsIgnoreCase(endDefToken);
+            boolean useEndDef1 = "endDef1".equalsIgnoreCase(endDefToken);
+
+            if (endDefToken == null || (!useEndDef1 && !useEndDef2)) {
+                throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS,
+                        "SET_PROPAGATE_DELETE patch for '" + typeDef.getName()
+                                + "' must set params." + PARAM_END_DEF_TOKEN + 
" to the string \"endDef1\" or \"endDef2\", got: "
+                                + endDefToken);
+            }
+
+            AtlasRelationshipDef updatedDef = new 
AtlasRelationshipDef(typeDef);
+
+            if (useEndDef2) {
+                AtlasRelationshipEndDef end2 = new 
AtlasRelationshipEndDef(updatedDef.getEndDef2());
+
+                end2.setIsPropagateDelete(true);
+                updatedDef.setEndDef2(end2);
+            } else {
+                AtlasRelationshipEndDef end1 = new 
AtlasRelationshipEndDef(updatedDef.getEndDef1());
+
+                end1.setIsPropagateDelete(true);
+                updatedDef.setEndDef1(end1);
+            }
+
+            updatedDef.setTypeVersion(patch.getUpdateToVersion());
+
+            typeDefStore.updateRelationshipDefByName(typeDef.getName(), 
updatedDef);
+
+            LOG.info("patch applied: id={}; action={}; relationshipType={}; 
endDefToken={}; typeVersion={}",
+                    patch.getId(), ACTION_SET_PROPAGATE_DELETE, 
typeDef.getName(), endDefToken, patch.getUpdateToVersion());
+
+            return APPLIED;
+        }
+
         /** If {@code params} contains {@code propagateAttributes}, coerces 
via {@link AtlasJson#getMapper()} {@code convertValue} to {@code 
List<Map<String,String>>}. */
         private static void 
setPropagateAttributesFromParamsIfPresent(Map<String, Object> params, 
AtlasRelationshipEndDef endDef) throws AtlasBaseException {
             if (MapUtils.isEmpty(params) || 
!params.containsKey(PARAM_PROPAGATE_ATTRIBUTES)) {
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
index 5041d28747..a34e4e3906 100644
--- 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/DeleteHandlerV1.java
@@ -39,6 +39,7 @@ import org.apache.atlas.repository.graphdb.AtlasGraph;
 import org.apache.atlas.repository.graphdb.AtlasUniqueKeyHandler;
 import org.apache.atlas.repository.graphdb.AtlasVertex;
 import org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2;
+import 
org.apache.atlas.repository.store.graph.v2.EntityDeletePropagationHandler;
 import org.apache.atlas.repository.store.graph.v2.EntityGraphRetriever;
 import org.apache.atlas.repository.store.graph.v2.tasks.ClassificationTask;
 import org.apache.atlas.tasks.TaskManagement;
@@ -143,6 +144,7 @@ public abstract class DeleteHandlerV1 {
     private final boolean              softDelete;
     private final TaskManagement       taskManagement;
     private final AtlasUniqueKeyHandler uniqueKeyHandler;
+    private final EntityDeletePropagationHandler deletePropagationHandler;
 
     public DeleteHandlerV1(AtlasGraph graph, AtlasTypeRegistry typeRegistry, 
boolean shouldUpdateInverseReference, boolean softDelete, TaskManagement 
taskManagement) {
         this.typeRegistry                  = typeRegistry;
@@ -152,6 +154,7 @@ public abstract class DeleteHandlerV1 {
         this.softDelete                    = softDelete;
         this.taskManagement                = taskManagement;
         this.uniqueKeyHandler              = graph.getUniqueKeyHandler();
+        this.deletePropagationHandler      = new 
EntityDeletePropagationHandler(typeRegistry);
     }
 
     /**
@@ -215,9 +218,46 @@ public abstract class DeleteHandlerV1 {
                 getColumnLineageEntities(instanceVertex, 
deletionCandidateVertices);
             }
         }
+
+        // Propagation applies only during delete (ACTIVE→DELETED), not purge 
(cleanup of already-DELETED entities)
+        if (!requestContext.isPurgeRequested()) {
+            collectDeletePropagationCandidates(requestContext, 
deletionCandidateVertices, instanceVertexGuids);
+        }
+
         return deletionCandidateVertices;
     }
 
+    private void collectDeletePropagationCandidates(RequestContext 
requestContext, Set<AtlasVertex> deletionCandidateVertices,
+                                                    Set<String> visitedGuids) 
throws AtlasBaseException {
+        Set<AtlasVertex> propagationSources = new 
HashSet<>(deletionCandidateVertices);
+
+        for (AtlasVertex vertex : propagationSources) {
+            String          typeName   = GraphHelper.getTypeName(vertex);
+            AtlasEntityType entityType = 
typeRegistry.getEntityTypeByName(typeName);
+
+            if (entityType == null || 
CollectionUtils.isEmpty(entityType.getDeletePropagationTargets())) {
+                continue;
+            }
+
+            LOG.debug("collectDeletePropagationCandidates(): checking 
propagation targets for entity type='{}', guid='{}'",
+                    typeName, AtlasGraphUtilsV2.getIdFromVertex(vertex));
+
+            Set<AtlasVertex> propagatedVertices = 
deletePropagationHandler.collectPropagatedVertices(vertex, entityType, 
visitedGuids);
+
+            for (AtlasVertex propagatedVertex : propagatedVertices) {
+                for (GraphHelper.VertexInfo vertexInfo : 
getOwnedVertices(propagatedVertex)) {
+                    requestContext.recordEntityDelete(vertexInfo.getEntity());
+                    deletionCandidateVertices.add(vertexInfo.getVertex());
+                }
+            }
+        }
+
+        if (deletionCandidateVertices.size() > propagationSources.size()) {
+            LOG.info("collectDeletePropagationCandidates(): added {} entities 
via delete propagation",
+                    deletionCandidateVertices.size() - 
propagationSources.size());
+        }
+    }
+
     /*
         actually delete traits and then the vertex along its references
     */
diff --git 
a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandler.java
 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandler.java
new file mode 100644
index 0000000000..a5c45b870a
--- /dev/null
+++ 
b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandler.java
@@ -0,0 +1,133 @@
+/**
+ * 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.repository.store.graph.v2;
+
+import org.apache.atlas.model.instance.AtlasEntity;
+import org.apache.atlas.repository.graph.GraphHelper;
+import org.apache.atlas.repository.graphdb.AtlasEdge;
+import org.apache.atlas.repository.graphdb.AtlasVertex;
+import org.apache.atlas.type.AtlasEntityType;
+import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
+import org.apache.atlas.type.AtlasTypeRegistry;
+import org.apache.atlas.type.DeletePropagationTarget;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Component
+public class EntityDeletePropagationHandler {
+    private static final Logger LOG = 
LoggerFactory.getLogger(EntityDeletePropagationHandler.class);
+
+    private final AtlasTypeRegistry typeRegistry;
+
+    @Inject
+    public EntityDeletePropagationHandler(AtlasTypeRegistry typeRegistry) {
+        this.typeRegistry = typeRegistry;
+    }
+
+    public Set<AtlasVertex> collectPropagatedVertices(AtlasVertex 
sourceVertex, AtlasEntityType entityType, Set<String> visitedGuids) {
+        Set<AtlasVertex> propagatedVertices = new HashSet<>();
+
+        collectPropagatedVerticesRecursive(sourceVertex, entityType, 
visitedGuids, propagatedVertices);
+
+        return propagatedVertices;
+    }
+
+    private void collectPropagatedVerticesRecursive(AtlasVertex sourceVertex, 
AtlasEntityType sourceEntityType,
+                                                    Set<String> visitedGuids, 
Set<AtlasVertex> propagatedVertices) {
+        for (DeletePropagationTarget propagationTarget : 
sourceEntityType.getDeletePropagationTargets()) {
+            AtlasAttribute relAttr = propagationTarget.getRelAttr();
+
+            if (relAttr == null) {
+                continue;
+            }
+
+            for (AtlasEdge edge : 
toList(GraphHelper.getEdgesForLabel(sourceVertex, 
relAttr.getRelationshipEdgeLabel(), relAttr.getRelationshipEdgeDirection()))) {
+                if (GraphHelper.getStatus(edge) == AtlasEntity.Status.DELETED) 
{
+                    LOG.debug("collectPropagatedVertices(): skip — 
soft-deleted edge (label={})", relAttr.getRelationshipEdgeLabel());
+                    continue;
+                }
+
+                AtlasVertex targetVertex = getOtherVertex(edge, sourceVertex);
+
+                if (targetVertex == null) {
+                    continue;
+                }
+
+                if (GraphHelper.getStatus(targetVertex) == 
AtlasEntity.Status.DELETED) {
+                    LOG.debug("collectPropagatedVertices(): skip — 
already-deleted target vertex (guid={})",
+                            AtlasGraphUtilsV2.getIdFromVertex(targetVertex));
+                    continue;
+                }
+
+                String targetGuid = 
AtlasGraphUtilsV2.getIdFromVertex(targetVertex);
+
+                if (StringUtils.isBlank(targetGuid) || 
!visitedGuids.add(targetGuid)) {
+                    LOG.debug("collectPropagatedVertices(): skip — already 
visited (guid={})", targetGuid);
+                    continue;
+                }
+
+                String          targetTypeName   = 
GraphHelper.getTypeName(targetVertex);
+                AtlasEntityType targetEntityType = 
typeRegistry.getEntityTypeByName(targetTypeName);
+
+                if (targetEntityType == null) {
+                    LOG.debug("collectPropagatedVertices(): skip — no typedef 
for target type '{}' (guid={})", targetTypeName, targetGuid);
+                    continue;
+                }
+
+                propagatedVertices.add(targetVertex);
+
+                LOG.info("collectPropagatedVertices(): propagating delete from 
'{}' to '{}' (guid={})",
+                        sourceEntityType.getTypeName(), targetTypeName, 
targetGuid);
+
+                if 
(CollectionUtils.isNotEmpty(targetEntityType.getDeletePropagationTargets())) {
+                    collectPropagatedVerticesRecursive(targetVertex, 
targetEntityType, visitedGuids, propagatedVertices);
+                }
+            }
+        }
+    }
+
+    private AtlasVertex getOtherVertex(AtlasEdge edge, AtlasVertex anchor) {
+        AtlasVertex out = edge.getOutVertex();
+        AtlasVertex in  = edge.getInVertex();
+
+        return out != null && out.equals(anchor) ? in : out;
+    }
+
+    private <T> List<T> toList(Iterator<T> iterator) {
+        List<T> list = new ArrayList<>();
+
+        if (iterator != null) {
+            while (iterator.hasNext()) {
+                list.add(iterator.next());
+            }
+        }
+
+        return list;
+    }
+}
diff --git 
a/repository/src/test/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializerTest.java
 
b/repository/src/test/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializerTest.java
index 8e43bcb1c2..6c1ce5fcfd 100644
--- 
a/repository/src/test/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializerTest.java
+++ 
b/repository/src/test/java/org/apache/atlas/repository/store/bootstrap/AtlasTypeDefStoreInitializerTest.java
@@ -929,20 +929,21 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerSupportedActions() {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerSupportedActions() {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         String[] actions = handler.getSupportedActions();
-        assertEquals(actions.length, 2);
+        assertEquals(actions.length, 3);
         assertEquals(actions[0], "SET_ATTRIBUTE_DEF_OVERRIDES");
         assertEquals(actions[1], "SET_PROPAGATE_RENAME");
+        assertEquals(actions[2], "SET_PROPAGATE_DELETE");
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerApplyOverrides() throws 
Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerApplyOverrides() 
throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_ATTRIBUTE_DEF_OVERRIDES", 
"TestEntity", "1.0", "2.0");
 
@@ -959,9 +960,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerApplyOverridesSkipped() 
throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerApplyOverridesSkipped()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_ATTRIBUTE_DEF_OVERRIDES", 
"TestEntity", "2.0", "3.0");
 
@@ -977,9 +978,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerApplyOverridesWrongType()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerApplyOverridesWrongType()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_ATTRIBUTE_DEF_OVERRIDES", 
"TestRelationship", "1.0", "2.0");
 
@@ -997,9 +998,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameEnd2WithPropagateAttributes()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameEnd2WithPropagateAttributes()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1031,9 +1032,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenamePropagateAttributesInvalidShape()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenamePropagateAttributesInvalidShape()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1053,9 +1054,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameEnd1() 
throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameEnd1()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1076,9 +1077,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameEnd2() 
throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameEnd2()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1099,9 +1100,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameInvalidEndDef()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameInvalidEndDef()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1120,9 +1121,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameMissingParam()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameMissingParam()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestRelationship", "1.0", "2.0");
 
@@ -1139,9 +1140,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerPropagateRenameWrongType()
 throws Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerPropagateRenameWrongType()
 throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_PROPAGATE_RENAME", 
"TestEntity", "1.0", "2.0");
 
@@ -1156,9 +1157,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerUnknownType() throws 
Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerUnknownType() 
throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_ATTRIBUTE_DEF_OVERRIDES", 
"UnknownType", "1.0", "2.0");
         when(typeRegistry.getTypeDefByName("UnknownType")).thenReturn(null);
@@ -1167,9 +1168,9 @@ public class AtlasTypeDefStoreInitializerTest {
     }
 
     @Test
-    public void 
testAttributeDefOverridesAndPropagateRenamePatchHandlerInvalidAction() throws 
Exception {
-        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler
 handler =
-                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndPropagateRenamePatchHandler(typeDefStore,
 typeRegistry);
+    public void 
testAttributeDefOverridesAndOperationPropagationPatchHandlerInvalidAction() 
throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
 
         Object patch = createMockTypeDefPatch("SET_ATTRIBUTE_DEF_OVERRIDES", 
"TestEntity", "1.0", "2.0");
         setField(patch, "action", "NOT_A_SUPPORTED_PATCH_ACTION");
@@ -1180,6 +1181,159 @@ public class AtlasTypeDefStoreInitializerTest {
         expectThrows(AtlasBaseException.class, () -> 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch));
     }
 
+    @Test
+    public void testPropagateDeletePatchHandlerApplyEnd1() throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "1.0", "2.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("endDefToken", "endDef1");
+        setField(patch, "params", params);
+
+        PatchStatus result = 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch);
+        assertEquals(result, APPLIED);
+
+        ArgumentCaptor<AtlasRelationshipDef> captor = 
ArgumentCaptor.forClass(AtlasRelationshipDef.class);
+        
verify(typeDefStore).updateRelationshipDefByName(eq("TestRelationship"), 
captor.capture());
+        assertTrue(captor.getValue().getEndDef1().getIsPropagateDelete());
+        assertFalse(captor.getValue().getEndDef2().getIsPropagateDelete());
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerApplyEnd2() throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "1.0", "2.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("endDefToken", "endDef2");
+        setField(patch, "params", params);
+
+        PatchStatus result = 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch);
+        assertEquals(result, APPLIED);
+
+        ArgumentCaptor<AtlasRelationshipDef> captor = 
ArgumentCaptor.forClass(AtlasRelationshipDef.class);
+        
verify(typeDefStore).updateRelationshipDefByName(eq("TestRelationship"), 
captor.capture());
+        assertFalse(captor.getValue().getEndDef1().getIsPropagateDelete());
+        assertTrue(captor.getValue().getEndDef2().getIsPropagateDelete());
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerInvalidEndDef() throws 
Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "1.0", "2.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("endDefToken", "endDef3");
+        setField(patch, "params", params);
+
+        expectThrows(AtlasBaseException.class, () -> 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch));
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerMissingParam() throws Exception 
{
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "1.0", "2.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        setField(patch, "params", new HashMap<String, Object>());
+
+        expectThrows(AtlasBaseException.class, () -> 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch));
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerNullParams() throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "1.0", "2.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        setField(patch, "params", null);
+
+        expectThrows(AtlasBaseException.class, () -> 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch));
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerWrongType() throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestEntity", "1.0", "2.0");
+
+        AtlasEntityDef entityDef = new AtlasEntityDef("TestEntity", "desc", 
"1.0");
+        
when(typeRegistry.getTypeDefByName("TestEntity")).thenReturn(entityDef);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("endDefToken", "endDef1");
+        setField(patch, "params", params);
+
+        expectThrows(AtlasBaseException.class, () -> 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch));
+    }
+
+    @Test
+    public void testPropagateDeletePatchHandlerSkipped() throws Exception {
+        
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler
 handler =
+                new 
AtlasTypeDefStoreInitializer.AttributeDefOverridesAndOperationPropagationPatchHandler(typeDefStore,
 typeRegistry);
+
+        Object patch = createMockTypeDefPatch("SET_PROPAGATE_DELETE", 
"TestRelationship", "2.0", "3.0");
+
+        AtlasRelationshipDef relationshipDef = new AtlasRelationshipDef();
+        relationshipDef.setName("TestRelationship");
+        relationshipDef.setTypeVersion("1.0");
+        relationshipDef.setEndDef1(new AtlasRelationshipEndDef("E1", "a1", 
AtlasAttributeDef.Cardinality.SINGLE));
+        relationshipDef.setEndDef2(new AtlasRelationshipEndDef("E2", "a2", 
AtlasAttributeDef.Cardinality.SINGLE));
+        
when(typeRegistry.getTypeDefByName("TestRelationship")).thenReturn(relationshipDef);
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("endDefToken", "endDef1");
+        setField(patch, "params", params);
+
+        PatchStatus result = 
handler.applyPatch((AtlasTypeDefStoreInitializer.TypeDefPatch) patch);
+        assertEquals(result, SKIPPED);
+        verify(typeDefStore, never()).updateRelationshipDefByName(anyString(), 
any());
+    }
+
     @Test
     public void testUpdateAttributeMetadataHandlerComprehensive() throws 
Exception {
         AtlasTypeDefStoreInitializer.UpdateAttributeMetadataHandler handler = 
new AtlasTypeDefStoreInitializer.UpdateAttributeMetadataHandler(typeDefStore, 
typeRegistry, graph);
diff --git 
a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandlerTest.java
 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandlerTest.java
new file mode 100644
index 0000000000..37769794d7
--- /dev/null
+++ 
b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/EntityDeletePropagationHandlerTest.java
@@ -0,0 +1,476 @@
+/**
+ * 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.repository.store.graph.v2;
+
+import org.apache.atlas.model.instance.AtlasEntity;
+import org.apache.atlas.repository.graph.GraphHelper;
+import org.apache.atlas.repository.graphdb.AtlasEdge;
+import org.apache.atlas.repository.graphdb.AtlasVertex;
+import org.apache.atlas.type.AtlasEntityType;
+import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
+import org.apache.atlas.type.AtlasTypeRegistry;
+import org.apache.atlas.type.DeletePropagationTarget;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class EntityDeletePropagationHandlerTest {
+    @Mock private AtlasTypeRegistry typeRegistry;
+
+    private EntityDeletePropagationHandler handler;
+
+    private MockedStatic<GraphHelper>       graphHelperMock;
+    private MockedStatic<AtlasGraphUtilsV2> graphUtilsMock;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+        handler = new EntityDeletePropagationHandler(typeRegistry);
+    }
+
+    @AfterMethod
+    public void tearDown() {
+        if (graphHelperMock != null) {
+            graphHelperMock.close();
+            graphHelperMock = null;
+        }
+        if (graphUtilsMock != null) {
+            graphUtilsMock.close();
+            graphUtilsMock = null;
+        }
+    }
+
+    @Test
+    public void testNoPropagationTargets_returnsEmpty() {
+        AtlasEntityType entityType = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex  = mock(AtlasVertex.class);
+
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.emptyList());
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testNullRelAttr_skipped() {
+        openStaticMocks();
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(null);
+
+        AtlasEntityType entityType = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex  = mock(AtlasVertex.class);
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testNoEdges_returnsEmpty() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        
when(relAttr.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType entityType = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex  = mock(AtlasVertex.class);
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.<AtlasEdge>emptyList().iterator());
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testDeletedEdge_skipped() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        
when(relAttr.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType entityType = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex  = mock(AtlasVertex.class);
+        AtlasEdge       edge       = mock(AtlasEdge.class);
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edge).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge)).thenReturn(AtlasEntity.Status.DELETED);
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testDeletedTargetVertex_skipped() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        
when(relAttr.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType entityType   = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex    = mock(AtlasVertex.class);
+        AtlasVertex     targetVertex = mock(AtlasVertex.class);
+        AtlasEdge       edge         = mock(AtlasEdge.class);
+
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+        when(edge.getOutVertex()).thenReturn(srcVertex);
+        when(edge.getInVertex()).thenReturn(targetVertex);
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edge).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(targetVertex)).thenReturn(AtlasEntity.Status.DELETED);
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testAlreadyVisitedGuid_skipped() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        
when(relAttr.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType entityType   = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex    = mock(AtlasVertex.class);
+        AtlasVertex     targetVertex = mock(AtlasVertex.class);
+        AtlasEdge       edge         = mock(AtlasEdge.class);
+
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+        when(edge.getOutVertex()).thenReturn(srcVertex);
+        when(edge.getInVertex()).thenReturn(targetVertex);
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edge).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(targetVertex)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(targetVertex)).thenReturn("already-visited-guid");
+
+        Set<String> visitedGuids = new HashSet<>();
+        visitedGuids.add("already-visited-guid");
+
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
entityType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testSingleTargetPropagated() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        
when(relAttr.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType sourceType   = mock(AtlasEntityType.class);
+        AtlasEntityType targetType   = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex    = mock(AtlasVertex.class);
+        AtlasVertex     targetVertex = mock(AtlasVertex.class);
+        AtlasEdge       edge         = mock(AtlasEdge.class);
+
+        
when(sourceType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+        when(sourceType.getTypeName()).thenReturn("trino_schema");
+        when(edge.getOutVertex()).thenReturn(srcVertex);
+        when(edge.getInVertex()).thenReturn(targetVertex);
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edge).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(targetVertex)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(targetVertex)).thenReturn("trino_table");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(targetVertex)).thenReturn("target-guid-001");
+
+        
when(typeRegistry.getEntityTypeByName("trino_table")).thenReturn(targetType);
+        
when(targetType.getDeletePropagationTargets()).thenReturn(Collections.emptyList());
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
sourceType, visitedGuids);
+
+        assertEquals(result.size(), 1);
+        assertTrue(result.contains(targetVertex));
+        assertTrue(visitedGuids.contains("target-guid-001"));
+    }
+
+    @Test
+    public void testRecursivePropagation() {
+        openStaticMocks();
+
+        // Level 1: trino_schema -> trino_table
+        AtlasAttribute relAttr1 = mock(AtlasAttribute.class);
+        
when(relAttr1.getRelationshipEdgeLabel()).thenReturn("__trino_table.schema");
+        when(relAttr1.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target1 = mock(DeletePropagationTarget.class);
+        when(target1.getRelAttr()).thenReturn(relAttr1);
+
+        // Level 2: trino_table -> trino_column
+        AtlasAttribute relAttr2 = mock(AtlasAttribute.class);
+        
when(relAttr2.getRelationshipEdgeLabel()).thenReturn("__trino_column.table");
+        when(relAttr2.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target2 = mock(DeletePropagationTarget.class);
+        when(target2.getRelAttr()).thenReturn(relAttr2);
+
+        AtlasEntityType schemaType  = mock(AtlasEntityType.class);
+        AtlasEntityType tableType   = mock(AtlasEntityType.class);
+        AtlasEntityType columnType  = mock(AtlasEntityType.class);
+        AtlasVertex     schemaVertex = mock(AtlasVertex.class);
+        AtlasVertex     tableVertex  = mock(AtlasVertex.class);
+        AtlasVertex     columnVertex = mock(AtlasVertex.class);
+        AtlasEdge       edge1        = mock(AtlasEdge.class);
+        AtlasEdge       edge2        = mock(AtlasEdge.class);
+
+        
when(schemaType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target1));
+        when(schemaType.getTypeName()).thenReturn("trino_schema");
+        
when(tableType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target2));
+        when(tableType.getTypeName()).thenReturn("trino_table");
+        
when(columnType.getDeletePropagationTargets()).thenReturn(Collections.emptyList());
+
+        // Edge from schema -> table
+        when(edge1.getOutVertex()).thenReturn(schemaVertex);
+        when(edge1.getInVertex()).thenReturn(tableVertex);
+        graphHelperMock.when(() -> 
GraphHelper.getEdgesForLabel(eq(schemaVertex), eq("__trino_table.schema"), 
any()))
+                .thenReturn(Collections.singletonList(edge1).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge1)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(tableVertex)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(tableVertex)).thenReturn("trino_table");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(tableVertex)).thenReturn("table-guid");
+
+        // Edge from table -> column
+        when(edge2.getOutVertex()).thenReturn(tableVertex);
+        when(edge2.getInVertex()).thenReturn(columnVertex);
+        graphHelperMock.when(() -> 
GraphHelper.getEdgesForLabel(eq(tableVertex), eq("__trino_column.table"), 
any()))
+                .thenReturn(Collections.singletonList(edge2).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge2)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(columnVertex)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(columnVertex)).thenReturn("trino_column");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(columnVertex)).thenReturn("column-guid");
+
+        
when(typeRegistry.getEntityTypeByName("trino_table")).thenReturn(tableType);
+        
when(typeRegistry.getEntityTypeByName("trino_column")).thenReturn(columnType);
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = 
handler.collectPropagatedVertices(schemaVertex, schemaType, visitedGuids);
+
+        assertEquals(result.size(), 2);
+        assertTrue(result.contains(tableVertex));
+        assertTrue(result.contains(columnVertex));
+    }
+
+    @Test
+    public void testCyclePrevention() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        when(relAttr.getRelationshipEdgeLabel()).thenReturn("__cyclic_edge");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType entityType = mock(AtlasEntityType.class);
+        AtlasVertex     vertexA    = mock(AtlasVertex.class);
+        AtlasVertex     vertexB    = mock(AtlasVertex.class);
+        AtlasEdge       edgeAtoB   = mock(AtlasEdge.class);
+        AtlasEdge       edgeBtoA   = mock(AtlasEdge.class);
+
+        
when(entityType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+        when(entityType.getTypeName()).thenReturn("cyclic_type");
+
+        // A -> B
+        when(edgeAtoB.getOutVertex()).thenReturn(vertexA);
+        when(edgeAtoB.getInVertex()).thenReturn(vertexB);
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(vertexA), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edgeAtoB).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edgeAtoB)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(vertexB)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(vertexB)).thenReturn("cyclic_type");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(vertexB)).thenReturn("guid-B");
+
+        // B -> A (cycle): vertexA guid is already in visitedGuids since it's 
the source
+        when(edgeBtoA.getOutVertex()).thenReturn(vertexB);
+        when(edgeBtoA.getInVertex()).thenReturn(vertexA);
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(vertexB), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edgeBtoA).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edgeBtoA)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(vertexA)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(vertexA)).thenReturn("guid-A");
+
+        
when(typeRegistry.getEntityTypeByName("cyclic_type")).thenReturn(entityType);
+
+        Set<String> visitedGuids = new HashSet<>();
+        visitedGuids.add("guid-A");
+
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(vertexA, 
entityType, visitedGuids);
+
+        assertEquals(result.size(), 1);
+        assertTrue(result.contains(vertexB));
+    }
+
+    @Test
+    public void testMultipleTargetsFromSameSource() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr1 = mock(AtlasAttribute.class);
+        when(relAttr1.getRelationshipEdgeLabel()).thenReturn("__edge_label_1");
+        when(relAttr1.getRelationshipEdgeDirection()).thenReturn(null);
+
+        AtlasAttribute relAttr2 = mock(AtlasAttribute.class);
+        when(relAttr2.getRelationshipEdgeLabel()).thenReturn("__edge_label_2");
+        when(relAttr2.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target1 = mock(DeletePropagationTarget.class);
+        when(target1.getRelAttr()).thenReturn(relAttr1);
+
+        DeletePropagationTarget target2 = mock(DeletePropagationTarget.class);
+        when(target2.getRelAttr()).thenReturn(relAttr2);
+
+        AtlasEntityType sourceType  = mock(AtlasEntityType.class);
+        AtlasEntityType targetType1 = mock(AtlasEntityType.class);
+        AtlasEntityType targetType2 = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex   = mock(AtlasVertex.class);
+        AtlasVertex     vertex1     = mock(AtlasVertex.class);
+        AtlasVertex     vertex2     = mock(AtlasVertex.class);
+        AtlasEdge       edge1       = mock(AtlasEdge.class);
+        AtlasEdge       edge2       = mock(AtlasEdge.class);
+
+        
when(sourceType.getDeletePropagationTargets()).thenReturn(Arrays.asList(target1,
 target2));
+        when(sourceType.getTypeName()).thenReturn("source_type");
+
+        when(edge1.getOutVertex()).thenReturn(srcVertex);
+        when(edge1.getInVertex()).thenReturn(vertex1);
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
eq("__edge_label_1"), any()))
+                .thenReturn(Collections.singletonList(edge1).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge1)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(vertex1)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(vertex1)).thenReturn("target_type_1");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(vertex1)).thenReturn("guid-1");
+
+        when(edge2.getOutVertex()).thenReturn(srcVertex);
+        when(edge2.getInVertex()).thenReturn(vertex2);
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
eq("__edge_label_2"), any()))
+                .thenReturn(Collections.singletonList(edge2).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge2)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(vertex2)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(vertex2)).thenReturn("target_type_2");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(vertex2)).thenReturn("guid-2");
+
+        
when(typeRegistry.getEntityTypeByName("target_type_1")).thenReturn(targetType1);
+        
when(typeRegistry.getEntityTypeByName("target_type_2")).thenReturn(targetType2);
+        
when(targetType1.getDeletePropagationTargets()).thenReturn(Collections.emptyList());
+        
when(targetType2.getDeletePropagationTargets()).thenReturn(Collections.emptyList());
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
sourceType, visitedGuids);
+
+        assertEquals(result.size(), 2);
+        assertTrue(result.contains(vertex1));
+        assertTrue(result.contains(vertex2));
+    }
+
+    @Test
+    public void testNullTargetEntityType_skipped() {
+        openStaticMocks();
+
+        AtlasAttribute relAttr = mock(AtlasAttribute.class);
+        when(relAttr.getRelationshipEdgeLabel()).thenReturn("__edge_label");
+        when(relAttr.getRelationshipEdgeDirection()).thenReturn(null);
+
+        DeletePropagationTarget target = mock(DeletePropagationTarget.class);
+        when(target.getRelAttr()).thenReturn(relAttr);
+
+        AtlasEntityType sourceType   = mock(AtlasEntityType.class);
+        AtlasVertex     srcVertex    = mock(AtlasVertex.class);
+        AtlasVertex     targetVertex = mock(AtlasVertex.class);
+        AtlasEdge       edge         = mock(AtlasEdge.class);
+
+        
when(sourceType.getDeletePropagationTargets()).thenReturn(Collections.singletonList(target));
+        when(edge.getOutVertex()).thenReturn(srcVertex);
+        when(edge.getInVertex()).thenReturn(targetVertex);
+
+        graphHelperMock.when(() -> GraphHelper.getEdgesForLabel(eq(srcVertex), 
anyString(), any()))
+                .thenReturn(Collections.singletonList(edge).iterator());
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(edge)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getStatus(targetVertex)).thenReturn(AtlasEntity.Status.ACTIVE);
+        graphHelperMock.when(() -> 
GraphHelper.getTypeName(targetVertex)).thenReturn("unknown_type");
+        graphUtilsMock.when(() -> 
AtlasGraphUtilsV2.getIdFromVertex(targetVertex)).thenReturn("unknown-guid");
+
+        
when(typeRegistry.getEntityTypeByName("unknown_type")).thenReturn(null);
+
+        Set<String> visitedGuids = new HashSet<>();
+        Set<AtlasVertex> result = handler.collectPropagatedVertices(srcVertex, 
sourceType, visitedGuids);
+
+        assertTrue(result.isEmpty());
+    }
+
+    private void openStaticMocks() {
+        graphHelperMock = mockStatic(GraphHelper.class);
+        graphUtilsMock  = mockStatic(AtlasGraphUtilsV2.class);
+    }
+}

Reply via email to