Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion addons/models/6000-Trino/6000-trino_model.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
"superTypes": [ "Asset" ],
"serviceType": "trino",
"typeVersion": "1.0",
"attributeDefOverrides": [
Comment thread
pinal-shah marked this conversation as resolved.
Outdated
{
"name": "qualifiedName",
"autoComputeFormat": "{table.trinoschema.catalog.name}.{table.trinoschema.name}.{table.name}.{name}@{table.trinoschema.catalog.instance.name}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since ".name" we have hardcoded as the name attribute of a particular type in the processing os autoComputeFormat, Please highlight or create a doc, on how to add the format for an entityType

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added section "Configuring autoComputeFormat on an entity type" in readme file.

}
],
"attributeDefs": [
{ "name": "parameters", "typeName": "map<string,string>", "cardinality": "SINGLE", "isUnique": false, "isIndexable": false, "isOptional": true }
]
Expand Down Expand Up @@ -124,7 +130,7 @@
"typeVersion": "1.0",
"relationshipCategory": "COMPOSITION",
"propagateTags": "NONE",
"endDef1": { "type": "trino_table", "name": "columns", "isContainer": true, "cardinality": "SET", "isLegacyAttribute": false },
"endDef1": { "type": "trino_table", "name": "columns", "isContainer": true, "cardinality": "SET", "isLegacyAttribute": false, "propagateRename": true },
"endDef2": { "type": "trino_column", "name": "table", "isContainer": false, "cardinality": "SINGLE", "isLegacyAttribute": false }
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
Comment thread
pinal-shah marked this conversation as resolved.
"patches": [
{
"id": "TYPEDEF_PATCH_6000_001_A",
"description": "Persist attributeDefOverrides for trino_column.qualifiedName (upgrade DBs at typeVersion 1.0)",
"action": "SET_ATTRIBUTE_DEF_OVERRIDES",
"typeName": "trino_column",
"applyToVersion": "1.0",
"updateToVersion": "1.1",
"attributeDefs": [
{
"name": "qualifiedName",
"autoComputeFormat": "{table.trinoschema.catalog.name}.{table.trinoschema.name}.{table.name}.{name}@{table.trinoschema.catalog.instance.name}"
}
]
},
{
"id": "TYPEDEF_PATCH_6000_001_B",
"description": "Set propagateRename=true on trino_table_columns endDef1 (upgrade DBs at typeVersion 1.0)",
"action": "SET_PROPAGATE_RENAME",
"typeName": "trino_table_columns",
"applyToVersion": "1.0",
"updateToVersion": "1.1",
"params": {
"endDefToken": "endDef1"
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public final class Constants {
public static final String TYPEVERSION_PROPERTY_KEY = getEncodedTypePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "type.version");
public static final String TYPEOPTIONS_PROPERTY_KEY = getEncodedTypePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "type.options");
public static final String TYPESERVICETYPE_PROPERTY_KEY = getEncodedTypePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "type.servicetype");
/**
* JSON-serialized {@code List<AtlasAttributeDef>} for entity typedef attribute overrides (e.g. qualifiedName format).
*/
public static final String TYPE_ATTR_DEF_OVERRIDES_PROPERTY_KEY = getEncodedTypePropertyKey(INTERNAL_PROPERTY_KEY_PREFIX + "type.attrDefOverrides");

// relationship def constants
public static final String RELATIONSHIPTYPE_END1_KEY = "endDef1";
Expand Down
98 changes: 98 additions & 0 deletions docs/src/documents/RenamePropagation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---

## 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 ends and templates), not by hard-coded type names in application code.

---

## 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.

---

## 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 context so they are saved with the root change.

---

## 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: relationship attribute, category, 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.

---

## 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.

Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public class AtlasEntityDef extends AtlasStructDef implements java.io.Serializab
// the value of this field is derived from all the businessMetadataDefs this entityType is referenced in
private Map<String, List<AtlasAttributeDef>> businessAttributeDefs;

// this field allows derived entity types to override specific properties (like autoComputeFormat)
// of attributes inherited from their superTypes, without needing to redefine the entire attribute.
private List<AtlasAttributeDef> attributeDefOverrides;

public AtlasEntityDef() {
this(null, null, null, null, null, null, null);
}
Expand Down Expand Up @@ -124,6 +128,7 @@ public AtlasEntityDef(AtlasEntityDef other) {
setSubTypes(other.getSubTypes());
setRelationshipAttributeDefs(other.getRelationshipAttributeDefs());
setBusinessAttributeDefs(other.getBusinessAttributeDefs());
setAttributeDefOverrides(other.getAttributeDefOverrides());
}
}

Expand Down Expand Up @@ -167,6 +172,14 @@ public void setBusinessAttributeDefs(Map<String, List<AtlasAttributeDef>> busine
this.businessAttributeDefs = businessAttributeDefs;
}

public List<AtlasAttributeDef> getAttributeDefOverrides() {
return attributeDefOverrides;
}

public void setAttributeDefOverrides(List<AtlasAttributeDef> attributeDefOverrides) {
this.attributeDefOverrides = attributeDefOverrides;
}

public boolean hasSuperType(String typeName) {
return hasSuperType(superTypes, typeName);
}
Expand Down Expand Up @@ -250,6 +263,20 @@ public StringBuilder toString(StringBuilder sb) {
}
}
sb.append('}');

if (CollectionUtils.isNotEmpty(attributeDefOverrides)) {
sb.append(", attributeDefOverrides=[");
int i = 0;
for (AtlasAttributeDef override : attributeDefOverrides) {
if (i > 0) {
sb.append(", ");
}
override.toString(sb);
i++;
}
sb.append(']');
}

sb.append('}');

return sb;
Expand All @@ -267,12 +294,13 @@ public boolean equals(Object o) {

AtlasEntityDef that = (AtlasEntityDef) o;

return Objects.equals(superTypes, that.superTypes);
return Objects.equals(superTypes, that.superTypes) &&
Objects.equals(attributeDefOverrides, that.attributeDefOverrides);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), superTypes);
return Objects.hash(super.hashCode(), superTypes, attributeDefOverrides);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef.Cardinality;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
Expand Down Expand Up @@ -69,6 +73,19 @@ public class AtlasRelationshipEndDef implements Serializable {
* Description of the end
*/
private String description;
/**
* When set this end acts as a trigger for rename propagation.
*/
private boolean propagateRename;

/**
* Optional mappings between attributes on the two entity types connected by this relationship,
* used when rename propagation applies updates across the relationship. Each element is a map
* with {@code "source"} and {@code "target"} keys naming the attribute on the source side and
* the attribute on the peer entity type, respectively. When {@code null} or empty, only the
* entity {@code name} attribute is synchronized.
*/
private List<Map<String, String>> propagateAttributes;

/**
* Base constructor
Expand Down Expand Up @@ -158,6 +175,8 @@ public AtlasRelationshipEndDef(AtlasRelationshipEndDef other) {
setCardinality(other.getCardinality());
setIsLegacyAttribute(other.isLegacyAttribute);
setDescription(other.description);
setIsPropagateRename(other.propagateRename);
setPropagateAttributes(other.propagateAttributes);
}
}

Expand Down Expand Up @@ -233,14 +252,16 @@ public StringBuilder toString(StringBuilder sb) {
sb.append(", isContainer==>'").append(isContainer).append('\'');
sb.append(", cardinality==>'").append(cardinality).append('\'');
sb.append(", isLegacyAttribute==>'").append(isLegacyAttribute).append('\'');
sb.append(", propagateRename==>'").append(propagateRename).append('\'');
sb.append(", propagateAttributes==>'").append(propagateAttributes).append('\'');
sb.append('}');

return sb;
}

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

@Override
Expand All @@ -258,7 +279,27 @@ public boolean equals(Object o) {
Objects.equals(description, that.description) &&
isContainer == that.isContainer &&
cardinality == that.cardinality &&
isLegacyAttribute == that.isLegacyAttribute;
isLegacyAttribute == that.isLegacyAttribute &&
propagateRename == that.propagateRename &&
Objects.equals(propagateAttributes, that.propagateAttributes);
}

@JsonProperty("propagateRename")
public boolean getIsPropagateRename() {
return propagateRename;
}

@JsonProperty("propagateRename")
public void setIsPropagateRename(boolean propagateRename) {
this.propagateRename = propagateRename;
}

public List<Map<String, String>> getPropagateAttributes() {
return propagateAttributes;
}

public void setPropagateAttributes(List<Map<String, String>> propagateAttributes) {
this.propagateAttributes = (propagateAttributes != null) ? Collections.unmodifiableList(propagateAttributes) : null;
}

@Override
Expand Down
Loading