From fbf8033b88255aea0cb125bb28a6c10ab93699e3 Mon Sep 17 00:00:00 2001 From: Umesh Patil Date: Sat, 27 Dec 2025 19:47:44 +0530 Subject: [PATCH 1/4] ATLAS_4988: Resolve slow deletion issue of Business Metadata with array-type attributes. --- .../v2/AtlasBusinessMetadataDefStoreV2.java | 64 ++++++++++++++----- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java index 5511935f918..8bceb102d8c 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java @@ -27,10 +27,12 @@ import org.apache.atlas.model.TypeCategory; import org.apache.atlas.model.discovery.AtlasSearchResult; import org.apache.atlas.model.discovery.SearchParameters; +import org.apache.atlas.model.instance.AtlasEntity; import org.apache.atlas.model.typedef.AtlasBaseTypeDef; import org.apache.atlas.model.typedef.AtlasBusinessMetadataDef; import org.apache.atlas.model.typedef.AtlasStructDef; import org.apache.atlas.repository.Constants; +import org.apache.atlas.repository.graphdb.AtlasGraph; import org.apache.atlas.repository.graphdb.AtlasVertex; import org.apache.atlas.type.AtlasBusinessMetadataType; import org.apache.atlas.type.AtlasStructType; @@ -58,6 +60,9 @@ public class AtlasBusinessMetadataDefStoreV2 extends AtlasAbstractDefStoreV2 attributeDefs = businessMetadataDef.getAttributeDefs(); + if (businessMetadataDef == null || CollectionUtils.isEmpty(businessMetadataDef.getAttributeDefs())) { + return; + } - for (AtlasStructDef.AtlasAttributeDef attributeDef : attributeDefs) { - String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(businessMetadataDef, attributeDef.getName()); - String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(businessMetadataDef, attributeDef, qualifiedName); - Set applicableTypes = AtlasJson.fromJson(attributeDef.getOption(AtlasBusinessMetadataDef.ATTR_OPTION_APPLICABLE_ENTITY_TYPES), Set.class); + for (AtlasStructDef.AtlasAttributeDef attributeDef : businessMetadataDef.getAttributeDefs()) { + String applicableTypesStr = attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES); + Set applicableTypes = StringUtils.isBlank(applicableTypesStr) ? null : AtlasJson.fromJson(applicableTypesStr, Set.class); - if (CollectionUtils.isNotEmpty(applicableTypes) && isBusinessAttributePresent(vertexPropertyName, applicableTypes)) { - throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, typeName); - } + if (CollectionUtils.isEmpty(applicableTypes)) { + continue; + } + + String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(businessMetadataDef, attributeDef.getName()); + String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(businessMetadataDef, attributeDef, qualifiedName); + + boolean isPresent; + + if (Boolean.TRUE.equals(attributeDef.getIsIndexable())) { + isPresent = isBusinessAttributePresent(vertexPropertyName, applicableTypes); + } else { + isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, applicableTypes); + } + + if (isPresent) { + throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, typeName); } } } private boolean isBusinessAttributePresent(String attrName, Set applicableTypes) throws AtlasBaseException { SearchParameters.FilterCriteria criteria = new SearchParameters.FilterCriteria(); - criteria.setAttributeName(attrName); - criteria.setOperator(SearchParameters.Operator.NOT_EMPTY); + criteria.setOperator(SearchParameters.Operator.NOT_NULL); SearchParameters.FilterCriteria entityFilters = new SearchParameters.FilterCriteria(); - entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR); entityFilters.setCriterion(Collections.singletonList(criteria)); SearchParameters searchParameters = new SearchParameters(); - searchParameters.setTypeName(String.join(SearchContext.TYPENAME_DELIMITER, applicableTypes)); searchParameters.setExcludeDeletedEntities(true); searchParameters.setIncludeSubClassifications(false); searchParameters.setEntityFilters(entityFilters); - searchParameters.setAttributes(Collections.singleton(attrName)); - searchParameters.setOffset(0); + searchParameters.setAttributes(Collections.emptySet()); searchParameters.setLimit(1); AtlasSearchResult atlasSearchResult = entityDiscoveryService.searchWithParameters(searchParameters); + return atlasSearchResult != null && CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); + } + + private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set applicableTypes) { + for (String typeName : applicableTypes) { + + Iterable vertices = graph.getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, typeName); - return CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); + if (vertices != null) { + for (AtlasVertex vertex : vertices) { + + if (vertex.getProperty(vertexPropertyName, Object.class) != null && + AtlasGraphUtilsV2.getState(vertex) == AtlasEntity.Status.ACTIVE) { + return true; + } + } + } + } + return false; } } From 5bf649f7baf7ab23d3c31e405115f79e6c488ea8 Mon Sep 17 00:00:00 2001 From: Umesh Patil Date: Tue, 30 Dec 2025 10:43:35 +0530 Subject: [PATCH 2/4] ATLAS-4988 : Replaced manual iteration with graph query, added error handling and logging. --- .../v2/AtlasBusinessMetadataDefStoreV2.java | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java index 8bceb102d8c..d22f4eac7cc 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java @@ -33,6 +33,7 @@ import org.apache.atlas.model.typedef.AtlasStructDef; import org.apache.atlas.repository.Constants; import org.apache.atlas.repository.graphdb.AtlasGraph; +import org.apache.atlas.repository.graphdb.AtlasGraphQuery; import org.apache.atlas.repository.graphdb.AtlasVertex; import org.apache.atlas.type.AtlasBusinessMetadataType; import org.apache.atlas.type.AtlasStructType; @@ -373,27 +374,37 @@ private void checkBusinessMetadataRef(String typeName) throws AtlasBaseException } for (AtlasStructDef.AtlasAttributeDef attributeDef : businessMetadataDef.getAttributeDefs()) { - String applicableTypesStr = attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES); - Set applicableTypes = StringUtils.isBlank(applicableTypesStr) ? null : AtlasJson.fromJson(applicableTypesStr, Set.class); + validateAttributeReferences(businessMetadataDef, attributeDef); + } + } - if (CollectionUtils.isEmpty(applicableTypes)) { - continue; - } + private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasStructDef.AtlasAttributeDef attributeDef) throws AtlasBaseException { + String applicableTypesStr = attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES); + Set applicableTypes = StringUtils.isBlank(applicableTypesStr) ? null : AtlasJson.fromJson(applicableTypesStr, Set.class); - String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(businessMetadataDef, attributeDef.getName()); - String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(businessMetadataDef, attributeDef, qualifiedName); + if (CollectionUtils.isEmpty(applicableTypes)) { + return; + } - boolean isPresent; + String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(bmDef, attributeDef.getName()); + String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(bmDef, attributeDef, qualifiedName); - if (Boolean.TRUE.equals(attributeDef.getIsIndexable())) { - isPresent = isBusinessAttributePresent(vertexPropertyName, applicableTypes); - } else { - isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, applicableTypes); - } + boolean isPresent; + long startTime = System.currentTimeMillis(); - if (isPresent) { - throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, typeName); - } + if (Boolean.TRUE.equals(attributeDef.getIsIndexable())) { + isPresent = isBusinessAttributePresent(vertexPropertyName, applicableTypes); + } else { + isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, applicableTypes); + } + + if (LOG.isDebugEnabled()) { + LOG.info("Reference check for attribute {} took {} ms. Found: {}", + attributeDef.getName(), (System.currentTimeMillis() - startTime), isPresent); + } + + if (isPresent) { + throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, bmDef.getName()); } } @@ -419,19 +430,29 @@ private boolean isBusinessAttributePresent(String attrName, Set applicab } private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set applicableTypes) { - for (String typeName : applicableTypes) { + if (graph == null || CollectionUtils.isEmpty(applicableTypes)) { + return false; + } - Iterable vertices = graph.getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, typeName); + try { + for (String typeName : applicableTypes) { - if (vertices != null) { - for (AtlasVertex vertex : vertices) { + Iterable vertices = graph.query() + .has(Constants.ENTITY_TYPE_PROPERTY_KEY, typeName) + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .has(Constants.STATE_PROPERTY_KEY, AtlasEntity.Status.ACTIVE.name()) + .vertices(); - if (vertex.getProperty(vertexPropertyName, Object.class) != null && - AtlasGraphUtilsV2.getState(vertex) == AtlasEntity.Status.ACTIVE) { - return true; + if (vertices != null && vertices.iterator().hasNext()) { + if (LOG.isDebugEnabled()) { + LOG.info("Found at least one reference for {} on type {}", vertexPropertyName, typeName); } + return true; } } + } catch (Exception e) { + LOG.error("Error occurred while querying graph for references of property: {}", vertexPropertyName, e); + return true; } return false; } From 8478f2b22dc0f140d135ff229c0997981eaf4d2c Mon Sep 17 00:00:00 2001 From: Umesh Patil Date: Mon, 12 Jan 2026 18:19:31 +0530 Subject: [PATCH 3/4] ATLAS-4988 : Removed entity-type filter from graph query,Ensured parent BM applicability is enforced across subtype inheritance etc. --- .../v2/AtlasBusinessMetadataDefStoreV2.java | 66 ++++++++++++------- .../graph/v2/AtlasTypeDefGraphStoreV2.java | 2 +- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java index d22f4eac7cc..5d793fdbc31 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java @@ -27,7 +27,6 @@ import org.apache.atlas.model.TypeCategory; import org.apache.atlas.model.discovery.AtlasSearchResult; import org.apache.atlas.model.discovery.SearchParameters; -import org.apache.atlas.model.instance.AtlasEntity; import org.apache.atlas.model.typedef.AtlasBaseTypeDef; import org.apache.atlas.model.typedef.AtlasBusinessMetadataDef; import org.apache.atlas.model.typedef.AtlasStructDef; @@ -38,6 +37,8 @@ import org.apache.atlas.type.AtlasBusinessMetadataType; import org.apache.atlas.type.AtlasStructType; import org.apache.atlas.type.AtlasType; +import org.apache.atlas.type.AtlasEntityType; +import org.apache.atlas.type.AtlasArrayType; import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.typesystem.types.DataTypes; import org.apache.atlas.utils.AtlasJson; @@ -53,6 +54,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.HashSet; import static org.apache.atlas.model.typedef.AtlasBusinessMetadataDef.ATTR_OPTION_APPLICABLE_ENTITY_TYPES; @@ -61,14 +63,14 @@ public class AtlasBusinessMetadataDefStoreV2 extends AtlasAbstractDefStoreV2 allApplicableTypes = getApplicableTypesWithSubTypes(applicableTypes); String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(bmDef, attributeDef.getName()); String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(bmDef, attributeDef, qualifiedName); boolean isPresent; long startTime = System.currentTimeMillis(); - if (Boolean.TRUE.equals(attributeDef.getIsIndexable())) { - isPresent = isBusinessAttributePresent(vertexPropertyName, applicableTypes); + if (isArrayAttribute(attributeDef)) { + isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, allApplicableTypes); } else { - isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, applicableTypes); + isPresent = isBusinessAttributePresent(qualifiedName, allApplicableTypes); } if (LOG.isDebugEnabled()) { @@ -411,7 +414,7 @@ private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasSt private boolean isBusinessAttributePresent(String attrName, Set applicableTypes) throws AtlasBaseException { SearchParameters.FilterCriteria criteria = new SearchParameters.FilterCriteria(); criteria.setAttributeName(attrName); - criteria.setOperator(SearchParameters.Operator.NOT_NULL); + criteria.setOperator(SearchParameters.Operator.NOT_EMPTY); SearchParameters.FilterCriteria entityFilters = new SearchParameters.FilterCriteria(); entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR); @@ -419,33 +422,30 @@ private boolean isBusinessAttributePresent(String attrName, Set applicab SearchParameters searchParameters = new SearchParameters(); searchParameters.setTypeName(String.join(SearchContext.TYPENAME_DELIMITER, applicableTypes)); - searchParameters.setExcludeDeletedEntities(true); + searchParameters.setExcludeDeletedEntities(false); searchParameters.setIncludeSubClassifications(false); searchParameters.setEntityFilters(entityFilters); - searchParameters.setAttributes(Collections.emptySet()); searchParameters.setLimit(1); AtlasSearchResult atlasSearchResult = entityDiscoveryService.searchWithParameters(searchParameters); return atlasSearchResult != null && CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); } - private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set applicableTypes) { - if (graph == null || CollectionUtils.isEmpty(applicableTypes)) { + private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set allApplicableTypes) { + if (graph == null || CollectionUtils.isEmpty(allApplicableTypes)) { return false; } try { - for (String typeName : applicableTypes) { - - Iterable vertices = graph.query() - .has(Constants.ENTITY_TYPE_PROPERTY_KEY, typeName) - .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) - .has(Constants.STATE_PROPERTY_KEY, AtlasEntity.Status.ACTIVE.name()) - .vertices(); + Iterable vertices = graph.query() + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .vertices(); - if (vertices != null && vertices.iterator().hasNext()) { + if (vertices != null) { + Iterator iterator = vertices.iterator(); + if (iterator.hasNext()) { if (LOG.isDebugEnabled()) { - LOG.info("Found at least one reference for {} on type {}", vertexPropertyName, typeName); + LOG.info("Found ARRAY BM reference for property {}",vertexPropertyName); } return true; } @@ -456,4 +456,26 @@ private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set } return false; } -} + + private boolean isArrayAttribute(AtlasStructDef.AtlasAttributeDef attrDef) throws AtlasBaseException { + AtlasType type = typeRegistry.getType(attrDef.getTypeName()); + return type instanceof AtlasArrayType; + } + + private Set getApplicableTypesWithSubTypes(Set applicableTypes) throws AtlasBaseException { + Set allTypes = new HashSet<>(); + + for (String typeName : applicableTypes) { + AtlasType type = typeRegistry.getType(typeName); + + if (type instanceof AtlasEntityType) { + AtlasEntityType entityType = (AtlasEntityType) type; + allTypes.add(entityType.getTypeName()); + allTypes.addAll(entityType.getAllSubTypes()); + } else { + allTypes.add(typeName); + } + } + return allTypes; + } +} \ No newline at end of file diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java index 57146521ab8..2ae2ec10164 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java @@ -177,7 +177,7 @@ protected AtlasDefStore getRelationshipDefStore(AtlasTypeR @Override protected AtlasDefStore getBusinessMetadataDefStore(AtlasTypeRegistry typeRegistry) { - return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.entityDiscoveryService); + return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.entityDiscoveryService, this.atlasGraph); } @Override From 0d5f0b899592939527bfeffe39fc80ca12b0a72b Mon Sep 17 00:00:00 2001 From: Umesh Patil Date: Fri, 6 Feb 2026 18:09:23 +0530 Subject: [PATCH 4/4] ATLASS-4988 : Added force delete validation logic for BusinessMetadata. --- .../java/org/apache/atlas/AtlasClientV2.java | 14 ++- .../java/org/apache/atlas/AtlasErrorCode.java | 4 +- .../repository/store/graph/AtlasDefStore.java | 20 +++- .../store/graph/AtlasTypeDefGraphStore.java | 18 ++- .../graph/v2/AtlasAbstractDefStoreV2.java | 30 ++++- .../v2/AtlasBusinessMetadataDefStoreV2.java | 110 +++++++++++------- .../apache/atlas/store/AtlasTypeDefStore.java | 2 +- .../graph/AtlasTypeDefGraphStoreTest.java | 8 +- .../org/apache/atlas/web/rest/TypesREST.java | 18 ++- .../atlas/web/adapters/TypeDefsRESTTest.java | 12 +- .../apache/atlas/web/rest/TypesRESTTest.java | 35 ++++-- 11 files changed, 188 insertions(+), 83 deletions(-) diff --git a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java index a485ea329f4..6d1d2360998 100644 --- a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java +++ b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java @@ -353,7 +353,19 @@ public void deleteAtlasTypeDefs(AtlasTypesDef typesDef) throws AtlasServiceExcep } public void deleteTypeByName(String typeName) throws AtlasServiceException { - callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + } + + public void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasServiceException { + if (forceDelete) { + + MultivaluedMap queryParams = new MultivaluedMapImpl(); + queryParams.add("force", "true"); + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, queryParams, typeName); + } else { + + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + } } // Entity APIs diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java index 9256d0f8865..b6997a09f65 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java +++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java @@ -257,7 +257,9 @@ public enum AtlasErrorCode { IMPORT_REGISTRATION_FAILED(500, "ATLAS-500-00-020", "Failed to register import request"), IMPORT_FAILED(500, "ATLAS-500-00-021", "Import with id {0} failed"), ABORT_IMPORT_FAILED(500, "ATLAS-500-00-022", "Failed to abort import with id {0}"), - IMPORT_QUEUEING_FAILED(500, "ATLAS-500-00-023", "Failed to add import with id {0} to request queue, please try again later"); + IMPORT_QUEUEING_FAILED(500, "ATLAS-500-00-023", "Failed to add import with id {0} to request queue, please try again later"), + + NON_INDEXABLE_BM_DELETE_NOT_ALLOWED(400, "ATLAS-400-00-106", "Deletion not allowed for non-indexable Business Metadata ''{0}'' without force-delete. Please use the force-delete parameter to remove."); private static final Logger LOG = LoggerFactory.getLogger(AtlasErrorCode.class); private final String errorCode; diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java index a956eecb41f..59828fd09e3 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java @@ -45,9 +45,25 @@ public interface AtlasDefStore { AtlasVertex preDeleteByName(String name) throws AtlasBaseException; - void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException; - AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException; + void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException; + void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws AtlasBaseException; + + default AtlasVertex preDeleteByName(String name, boolean forceDelete) throws AtlasBaseException { + return preDeleteByName(name); + } + + default void deleteByName(String name, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + deleteByName(name, preDeleteResult); + } + + default AtlasVertex preDeleteByGuid(String guid, boolean forceDelete) throws AtlasBaseException { + return preDeleteByGuid(guid); + } + + default void deleteByGuid(String guid, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + deleteByGuid(guid, preDeleteResult); + } } diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java index 4776912cd13..7213a3fec8d 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java @@ -539,14 +539,20 @@ public AtlasTypesDef createUpdateTypesDef(AtlasTypesDef typesToCreate, AtlasType @Override @GraphTransaction public void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException { + deleteTypesDef(typesDef, false); + } + + @GraphTransaction + public void deleteTypesDef(AtlasTypesDef typesDef, boolean forceDelete) throws AtlasBaseException { if (LOG.isDebugEnabled()) { - LOG.debug("==> AtlasTypeDefGraphStore.deleteTypesDef(enums={}, structs={}, classfications={}, entities={}, relationships={}, businessMetadataDefs={})", + LOG.debug("==> AtlasTypeDefGraphStore.deleteTypesDef(enums={}, structs={}, classfications={}, entities={}, relationships={}, businessMetadataDefs={}, forceDelete={})", CollectionUtils.size(typesDef.getEnumDefs()), CollectionUtils.size(typesDef.getStructDefs()), CollectionUtils.size(typesDef.getClassificationDefs()), CollectionUtils.size(typesDef.getEntityDefs()), CollectionUtils.size(typesDef.getRelationshipDefs()), - CollectionUtils.size(typesDef.getBusinessMetadataDefs())); + CollectionUtils.size(typesDef.getBusinessMetadataDefs()), + forceDelete); } AtlasTransientTypeRegistry ttr = lockTypeRegistryAndReleasePostCommit(); @@ -678,9 +684,9 @@ public void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException { if (CollectionUtils.isNotEmpty(typesDef.getBusinessMetadataDefs())) { for (AtlasBusinessMetadataDef businessMetadataDef : typesDef.getBusinessMetadataDefs()) { if (StringUtils.isNotBlank(businessMetadataDef.getGuid())) { - businessMetadataDefStore.deleteByGuid(businessMetadataDef.getGuid(), null); + businessMetadataDefStore.deleteByGuid(businessMetadataDef.getGuid(), null, forceDelete); } else { - businessMetadataDefStore.deleteByName(businessMetadataDef.getName(), null); + businessMetadataDefStore.deleteByName(businessMetadataDef.getName(), null, forceDelete); } } } @@ -777,7 +783,7 @@ public AtlasBaseTypeDef getByGuid(String guid) throws AtlasBaseException { @Override @GraphTransaction - public void deleteTypeByName(String typeName) throws AtlasBaseException { + public void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasBaseException { AtlasType atlasType = typeRegistry.getType(typeName); if (atlasType == null) { @@ -801,7 +807,7 @@ public void deleteTypeByName(String typeName) throws AtlasBaseException { typesDef.setStructDefs(Collections.singletonList((AtlasStructDef) baseTypeDef)); } - deleteTypesDef(typesDef); + deleteTypesDef(typesDef, forceDelete); } @Override diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java index 1d61aa9785a..644676884a5 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java @@ -148,24 +148,46 @@ public boolean isValidName(String typeName) { @Override public void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException { - LOG.debug("==> AtlasAbstractDefStoreV1.deleteByName({}, {})", name, preDeleteResult); + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByName({}, {})", name, preDeleteResult); AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByName(name) : preDeleteResult; typeDefStore.deleteTypeVertex(vertex); - LOG.debug("<== AtlasAbstractDefStoreV1.deleteByName({}, {})", name, preDeleteResult); + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByName({}, {})", name, preDeleteResult); } @Override public void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws AtlasBaseException { - LOG.debug("==> AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid, preDeleteResult); + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByGuid({}, {})", guid, preDeleteResult); AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid) : preDeleteResult; typeDefStore.deleteTypeVertex(vertex); - LOG.debug("<== AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid, preDeleteResult); + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByGuid({}, {})", guid, preDeleteResult); + } + + @Override + public void deleteByName(String name, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByName({}, {}, {})", name, preDeleteResult, forceDelete); + + AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByName(name, forceDelete) : preDeleteResult; + + typeDefStore.deleteTypeVertex(vertex); + + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByName({}, {}, {})", name, preDeleteResult, forceDelete); + } + + @Override + public void deleteByGuid(String guid, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByGuid({}, {}, {})", guid, preDeleteResult, forceDelete); + + AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid, forceDelete) : preDeleteResult; + + typeDefStore.deleteTypeVertex(vertex); + + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByGuid({}, {}, {})", guid, preDeleteResult, forceDelete); } public boolean isInvalidTypeDefName(String typeName) { diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java index 5d793fdbc31..d58fcfa62c4 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java @@ -266,8 +266,14 @@ public AtlasBusinessMetadataDef updateByGuid(String guid, AtlasBusinessMetadataD return ret; } + @Override public AtlasVertex preDeleteByName(String name) throws AtlasBaseException { - LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByName({})", name); + return preDeleteByName(name, false); + } + + @Override + public AtlasVertex preDeleteByName(String name, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByName({}, {})", name, forceDelete); AtlasBusinessMetadataDef existingDef = typeRegistry.getBusinessMetadataDefByName(name); @@ -279,15 +285,29 @@ public AtlasVertex preDeleteByName(String name) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_NOT_FOUND, name); } - checkBusinessMetadataRef(existingDef.getName()); + if (!forceDelete) { + boolean hasIndexableAttribute = hasIndexableAttribute(existingDef); + + if (!hasIndexableAttribute) { + LOG.warn("Deletion blocked for non-indexable Business Metadata '{}' without force-delete flag", name); + throw new AtlasBaseException(AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED, name); + } + checkBusinessMetadataRef(existingDef.getName()); + } - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByName({}): {}", name, ret); + LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByName({}, {}): {}", name, forceDelete, ret); return ret; } + @Override public AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException { - LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({})", guid); + return preDeleteByGuid(guid, false); + } + + @Override + public AtlasVertex preDeleteByGuid(String guid, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}, {})", guid, forceDelete); AtlasBusinessMetadataDef existingDef = typeRegistry.getBusinessMetadataDefByGuid(guid); @@ -299,15 +319,35 @@ public AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_GUID_NOT_FOUND, guid); } - if (existingDef != null) { + if (existingDef != null && !forceDelete) { + boolean hasIndexableAttribute = hasIndexableAttribute(existingDef); + + if (!hasIndexableAttribute) { + LOG.warn("Deletion blocked for non-indexable Business Metadata '{}' without force-delete flag", existingDef.getName()); + throw new AtlasBaseException(AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED, existingDef.getName()); + } checkBusinessMetadataRef(existingDef.getName()); } - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}): ret={}", guid, ret); + LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}, {}): ret={}", guid, forceDelete, ret); return ret; } + private boolean hasIndexableAttribute(AtlasBusinessMetadataDef bmDef) { + if (bmDef == null || CollectionUtils.isEmpty(bmDef.getAttributeDefs())) { + return false; + } + + for (AtlasStructDef.AtlasAttributeDef attributeDef : bmDef.getAttributeDefs()) { + if (attributeDef.getIsIndexable()) { + return true; + } + } + + return false; + } + @Override public void validateType(AtlasBaseTypeDef typeDef) throws AtlasBaseException { super.validateType(typeDef); @@ -392,14 +432,9 @@ private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasSt String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(bmDef, attributeDef.getName()); String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(bmDef, attributeDef, qualifiedName); - boolean isPresent; long startTime = System.currentTimeMillis(); - if (isArrayAttribute(attributeDef)) { - isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, allApplicableTypes); - } else { - isPresent = isBusinessAttributePresent(qualifiedName, allApplicableTypes); - } + boolean isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, allApplicableTypes); if (LOG.isDebugEnabled()) { LOG.info("Reference check for attribute {} took {} ms. Found: {}", @@ -411,25 +446,6 @@ private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasSt } } - private boolean isBusinessAttributePresent(String attrName, Set applicableTypes) throws AtlasBaseException { - SearchParameters.FilterCriteria criteria = new SearchParameters.FilterCriteria(); - criteria.setAttributeName(attrName); - criteria.setOperator(SearchParameters.Operator.NOT_EMPTY); - - SearchParameters.FilterCriteria entityFilters = new SearchParameters.FilterCriteria(); - entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR); - entityFilters.setCriterion(Collections.singletonList(criteria)); - - SearchParameters searchParameters = new SearchParameters(); - searchParameters.setTypeName(String.join(SearchContext.TYPENAME_DELIMITER, applicableTypes)); - searchParameters.setExcludeDeletedEntities(false); - searchParameters.setIncludeSubClassifications(false); - searchParameters.setEntityFilters(entityFilters); - searchParameters.setLimit(1); - - AtlasSearchResult atlasSearchResult = entityDiscoveryService.searchWithParameters(searchParameters); - return atlasSearchResult != null && CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); - } private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set allApplicableTypes) { if (graph == null || CollectionUtils.isEmpty(allApplicableTypes)) { @@ -437,18 +453,27 @@ private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set } try { - Iterable vertices = graph.query() + List typesList = new ArrayList<>(allApplicableTypes); + + // 1. To Check if the BM property exists on a vertex where the direct type matches + Iterable verticesDirect = graph.query() .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .in(Constants.ENTITY_TYPE_PROPERTY_KEY, typesList) .vertices(); - if (vertices != null) { - Iterator iterator = vertices.iterator(); - if (iterator.hasNext()) { - if (LOG.isDebugEnabled()) { - LOG.info("Found ARRAY BM reference for property {}",vertexPropertyName); - } - return true; - } + if (verticesDirect != null && verticesDirect.iterator().hasNext()) { + return true; + } + + // 2. To Check if the BM property exists on a vertex where it inherits from one of parent Types + // This is crucial for Case 6 (Parent -> Child) + Iterable verticesInherited = graph.query() + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .in(Constants.SUPER_TYPES_PROPERTY_KEY, typesList) + .vertices(); + + if (verticesInherited != null && verticesInherited.iterator().hasNext()) { + return true; } } catch (Exception e) { LOG.error("Error occurred while querying graph for references of property: {}", vertexPropertyName, e); @@ -457,11 +482,6 @@ private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set return false; } - private boolean isArrayAttribute(AtlasStructDef.AtlasAttributeDef attrDef) throws AtlasBaseException { - AtlasType type = typeRegistry.getType(attrDef.getTypeName()); - return type instanceof AtlasArrayType; - } - private Set getApplicableTypesWithSubTypes(Set applicableTypes) throws AtlasBaseException { Set allTypes = new HashSet<>(); diff --git a/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java b/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java index 792f714ad3f..6c38f27fde3 100644 --- a/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java +++ b/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java @@ -109,7 +109,7 @@ AtlasClassificationDef updateClassificationDefByGuid(String guid, AtlasClassific AtlasBaseTypeDef getByGuid(String guid) throws AtlasBaseException; - void deleteTypeByName(String typeName) throws AtlasBaseException; + void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasBaseException; void notifyLoadCompletion(); } diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java index f84d1f4610e..90603af0341 100644 --- a/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java @@ -369,10 +369,10 @@ public void deleteTypeByName() throws IOException { AtlasTypesDef typesDef = TestResourceFileUtils.readObjectFromJson(".", hivedbV2Json, AtlasTypesDef.class); typeDefStore.createTypesDef(typesDef); - typeDefStore.deleteTypeByName(hiveDB2); - typeDefStore.deleteTypeByName(relationshipDefName); - typeDefStore.deleteTypeByName(hostEntityDef); - typeDefStore.deleteTypeByName(clusterEntityDef); + typeDefStore.deleteTypeByName(hiveDB2, false); + typeDefStore.deleteTypeByName(relationshipDefName, false); + typeDefStore.deleteTypeByName(hostEntityDef, false); + typeDefStore.deleteTypeByName(clusterEntityDef, false); } catch (AtlasBaseException e) { fail("Deletion should've succeeded"); } diff --git a/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java index e42b7627b11..9c7b254c6b3 100644 --- a/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java +++ b/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java @@ -49,6 +49,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.DefaultValue; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -426,22 +428,30 @@ public void deleteAtlasTypeDefs(final AtlasTypesDef typesDef) throws AtlasBaseEx /** * Delete API for type identified by its name. * @param typeName Name of the type to be deleted. + * @param forceDelete If true, bypasses pre-delete validation checks and forcefully removes the type definition. + * For BusinessMetadata types: + * - If isIndexable=true and force=false: performs normal graph scan validation + * - If isIndexable=false and force=false: blocks deletion (requires force=true) + * - If force=true: skips all validation and deletes the type + * Defaults to false for backward compatibility. * @throws AtlasBaseException * @HTTP 204 On successful deletion of the requested type definitions - * @HTTP 400 On validation failure for any type definitions + * @HTTP 400 On validation failure for any type definitions or when attempting to delete + * non-indexable BusinessMetadata without force-delete parameter */ @DELETE @Path("/typedef/name/{typeName}") @Timed - public void deleteAtlasTypeByName(@PathParam("typeName") final String typeName) throws AtlasBaseException { + public void deleteAtlasTypeByName(@PathParam("typeName") final String typeName, + @QueryParam("force") @DefaultValue("false") final boolean forceDelete) throws AtlasBaseException { AtlasPerfTracer perf = null; try { if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { - perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeByName(" + typeName + ")"); + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeByName(" + typeName + ", force=" + forceDelete + ")"); } - typeDefStore.deleteTypeByName(typeName); + typeDefStore.deleteTypeByName(typeName, forceDelete); } finally { AtlasPerfTracer.log(perf); } diff --git a/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java b/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java index 7859f88181f..5078047e520 100644 --- a/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java @@ -108,7 +108,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { AtlasErrorCode errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -124,7 +124,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -135,7 +135,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { entityREST.removeBusinessAttributes(dbEntity.getGuid(), bmWithAllTypes, objectMap); - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } @Test @@ -155,7 +155,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { AtlasErrorCode errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -171,7 +171,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -182,7 +182,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { entityREST.removeBusinessAttributes(dbEntity.getGuid(), bmWithSuperType, objectMap); - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } @Test diff --git a/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java b/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java index 257a4b5ec58..2228c3586a2 100644 --- a/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java @@ -593,16 +593,16 @@ public void testDeleteAtlasTypeDefs_WithException() throws AtlasBaseException { public void testDeleteAtlasTypeByName_Success() throws AtlasBaseException { // Setup String typeName = "test_type"; - doNothing().when(typeDefStore).deleteTypeByName(typeName); + doNothing().when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); // Execute - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); // Verify - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); } } @@ -610,17 +610,17 @@ public void testDeleteAtlasTypeByName_Success() throws AtlasBaseException { public void testDeleteAtlasTypeByName_WithPerfTracerEnabled() throws AtlasBaseException { // Setup String typeName = "test_type_perf"; - doNothing().when(typeDefStore).deleteTypeByName(typeName); + doNothing().when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(true); mockedPerfTracer.when(() -> AtlasPerfTracer.getPerfTracer(any(), anyString())).thenReturn(null); // Execute - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); // Verify - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); mockedPerfTracer.verify(() -> AtlasPerfTracer.isPerfTraceEnabled(any())); mockedPerfTracer.verify(() -> AtlasPerfTracer.getPerfTracer(any(), anyString())); } @@ -631,18 +631,35 @@ public void testDeleteAtlasTypeByName_WithException() throws AtlasBaseException // Setup String typeName = "test_type_error"; AtlasBaseException exception = new AtlasBaseException("Delete by name failed"); - doThrow(exception).when(typeDefStore).deleteTypeByName(typeName); + doThrow(exception).when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); // Execute & Verify AtlasBaseException thrownException = expectThrows(AtlasBaseException.class, () -> { - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); }); assertEquals(thrownException.getMessage(), "Delete by name failed"); - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); + } + } + + @Test + public void testDeleteAtlasTypeByName_WithForceDelete() throws AtlasBaseException { + // Setup + String typeName = "test_type_force"; + doNothing().when(typeDefStore).deleteTypeByName(typeName, true); + + try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { + mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); + + // Execute with force=true + typesREST.deleteAtlasTypeByName(typeName, true); + + // Verify + verify(typeDefStore).deleteTypeByName(typeName, true); } }