From a5f7bc54dd5717883328102f0289d1cb6d3dbe64 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Tue, 7 Apr 2026 01:41:19 +0530 Subject: [PATCH 01/29] feat: implement fast FQN prefix-based bulk deletion to fix race conditions and slow cascade Replaces one-entity-at-a-time cascade deletion with indexed LIKE-prefix bulk deletion. Works transparently at any hierarchy level (service, database, schema, table). Key changes: - entity_relationship: add fromFQNHash/toFQNHash columns + indexes (MySQL + Postgres) - SQL migration 1.14.1: schema changes + Java backfill migration for existing rows - EntityRelationshipDAO: 8-arg insert with COALESCE, prefix-delete methods, backfill methods - EntityDAO: findIdsByFqnHashPrefix + deleteBatch default methods - EntityTimeSeriesDAO: deleteByFqnHashPrefix default method - EntityRepository: FQN-bearing addRelationship overload, addServiceRelationship passes FQN hashes, deleteTimeSeriesByFqnPrefix hook, getLockManager/callPreDelete/invalidateEntity - TableRepository, DatabaseSchemaRepository: storeRelationships passes FQN strings - FeedRepository: deleteByAboutBatch for bulk thread deletion - CollectionDAO: UsageDAO.deleteBatch, FeedDAO.findByEntityIds - PrefixDeletionService (new): 7-phase hard delete orchestration with lock management - LockManagerInitializer: also initializes PrefixDeletionService - EntityResource: deletePrefixHardById() async protected method - DatabaseServiceResource: DELETE /prefix/{id} endpoint (202 Accepted) Co-Authored-By: Claude Sonnet 4.6 --- .../native/1.14.1/mysql/schemaChanges.sql | 9 + .../native/1.14.1/postgres/schemaChanges.sql | 9 + bootstrap/sql/schema/mysql.sql | 6 +- bootstrap/sql/schema/postgres.sql | 18 +- .../LockManagerInitializer.java | 4 + .../service/jdbi3/CollectionDAO.java | 59 +++++- .../jdbi3/DatabaseSchemaRepository.java | 6 +- .../openmetadata/service/jdbi3/EntityDAO.java | 23 ++ .../service/jdbi3/EntityRepository.java | 52 ++++- .../service/jdbi3/EntityTimeSeriesDAO.java | 11 + .../service/jdbi3/FeedRepository.java | 16 ++ .../service/jdbi3/PrefixDeletionService.java | 196 ++++++++++++++++++ .../service/jdbi3/TableRepository.java | 7 +- .../migration/mysql/v1141/Migration.java | 41 ++++ .../migration/postgres/v1141/Migration.java | 41 ++++ .../migration/utils/v1141/MigrationUtil.java | 93 +++++++++ .../service/resources/EntityResource.java | 38 ++++ .../database/DatabaseServiceResource.java | 24 +++ 18 files changed, 636 insertions(+), 17 deletions(-) create mode 100644 bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql create mode 100644 bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql create mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java create mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java create mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java create mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java diff --git a/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql new file mode 100644 index 000000000000..16e8c246ab13 --- /dev/null +++ b/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql @@ -0,0 +1,9 @@ +-- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. +-- This allows deleting all relationships for an entire entity subtree in a single indexed query +-- instead of walking the tree entity-by-entity. +ALTER TABLE entity_relationship + ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, + ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; + +CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); +CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); diff --git a/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql new file mode 100644 index 000000000000..4fdc9d342ede --- /dev/null +++ b/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql @@ -0,0 +1,9 @@ +-- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. +-- This allows deleting all relationships for an entire entity subtree in a single indexed query +-- instead of walking the tree entity-by-entity. +ALTER TABLE entity_relationship + ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, + ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; + +CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash); +CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash); diff --git a/bootstrap/sql/schema/mysql.sql b/bootstrap/sql/schema/mysql.sql index c76d44ffb301..b809fcff2c13 100644 --- a/bootstrap/sql/schema/mysql.sql +++ b/bootstrap/sql/schema/mysql.sql @@ -352,9 +352,13 @@ CREATE TABLE `entity_relationship` ( `jsonSchema` varchar(256) DEFAULT NULL, `json` json DEFAULT NULL, `deleted` tinyint(1) NOT NULL DEFAULT '0', + `fromFQNHash` varchar(768) DEFAULT NULL, + `toFQNHash` varchar(768) DEFAULT NULL, PRIMARY KEY (`fromId`,`toId`,`relation`), KEY `from_index` (`fromId`,`relation`), - KEY `to_index` (`toId`,`relation`) + KEY `to_index` (`toId`,`relation`), + KEY `idx_er_from_fqn_hash` (`fromFQNHash`(768)), + KEY `idx_er_to_fqn_hash` (`toFQNHash`(768)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/bootstrap/sql/schema/postgres.sql b/bootstrap/sql/schema/postgres.sql index 0c6b64b116ed..7336a90ba23a 100644 --- a/bootstrap/sql/schema/postgres.sql +++ b/bootstrap/sql/schema/postgres.sql @@ -325,7 +325,9 @@ CREATE TABLE public.entity_relationship ( relation smallint NOT NULL, jsonschema character varying(256), json jsonb, - deleted boolean DEFAULT false NOT NULL + deleted boolean DEFAULT false NOT NULL, + fromfqnhash character varying(768), + tofqnhash character varying(768) ); @@ -1921,6 +1923,20 @@ CREATE INDEX entity_relationship_from_index ON public.entity_relationship USING CREATE INDEX entity_relationship_to_index ON public.entity_relationship USING btree (toid, relation); +-- +-- Name: idx_er_from_fqn_hash; Type: INDEX; Schema: public; Owner: openmetadata_user +-- + +CREATE INDEX idx_er_from_fqn_hash ON public.entity_relationship USING btree (fromfqnhash); + + +-- +-- Name: idx_er_to_fqn_hash; Type: INDEX; Schema: public; Owner: openmetadata_user +-- + +CREATE INDEX idx_er_to_fqn_hash ON public.entity_relationship USING btree (tofqnhash); + + -- -- Name: field_relationship_from_index; Type: INDEX; Schema: public; Owner: openmetadata_user -- diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/initialization/LockManagerInitializer.java b/openmetadata-service/src/main/java/org/openmetadata/service/initialization/LockManagerInitializer.java index 93cab31a2be0..4886d7837ff9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/initialization/LockManagerInitializer.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/initialization/LockManagerInitializer.java @@ -3,6 +3,7 @@ import lombok.extern.slf4j.Slf4j; import org.openmetadata.service.Entity; import org.openmetadata.service.jdbi3.EntityRepository; +import org.openmetadata.service.jdbi3.PrefixDeletionService; import org.openmetadata.service.lock.HierarchicalLockManager; /** @@ -44,6 +45,9 @@ public static void initialize() { // Set it on EntityRepository EntityRepository.setLockManager(lockManager); + // Initialize PrefixDeletionService with the same lock manager + PrefixDeletionService.initialize(lockManager); + initialized = true; LOG.info("Hierarchical lock manager initialized successfully"); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 561e405b7dc9..5cef80ac385b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -1556,15 +1556,21 @@ default void bulkRemoveFromRelationship( @ConnectionAwareSqlUpdate( value = - "INSERT INTO entity_relationship(fromId, toId, fromEntity, toEntity, relation, json) " - + "VALUES (:fromId, :toId, :fromEntity, :toEntity, :relation, :json) " - + "ON DUPLICATE KEY UPDATE json = :json", + "INSERT INTO entity_relationship" + + "(fromId, toId, fromEntity, toEntity, relation, json, fromFQNHash, toFQNHash) " + + "VALUES (:fromId, :toId, :fromEntity, :toEntity, :relation, :json, :fromFQNHash, :toFQNHash) " + + "ON DUPLICATE KEY UPDATE json = :json, " + + "fromFQNHash = COALESCE(:fromFQNHash, fromFQNHash), " + + "toFQNHash = COALESCE(:toFQNHash, toFQNHash)", connectionType = MYSQL) @ConnectionAwareSqlUpdate( value = - "INSERT INTO entity_relationship(fromId, toId, fromEntity, toEntity, relation, json) VALUES " - + "(:fromId, :toId, :fromEntity, :toEntity, :relation, (:json :: jsonb)) " - + "ON CONFLICT (fromId, toId, relation) DO UPDATE SET json = EXCLUDED.json", + "INSERT INTO entity_relationship" + + "(fromId, toId, fromEntity, toEntity, relation, json, fromFQNHash, toFQNHash) " + + "VALUES (:fromId, :toId, :fromEntity, :toEntity, :relation, (:json :: jsonb), :fromFQNHash, :toFQNHash) " + + "ON CONFLICT (fromId, toId, relation) DO UPDATE SET json = EXCLUDED.json, " + + "fromFQNHash = COALESCE(EXCLUDED.fromFQNHash, entity_relationship.fromFQNHash), " + + "toFQNHash = COALESCE(EXCLUDED.toFQNHash, entity_relationship.toFQNHash)", connectionType = POSTGRES) void insert( @BindUUID("fromId") UUID fromId, @@ -1572,7 +1578,14 @@ void insert( @Bind("fromEntity") String fromEntity, @Bind("toEntity") String toEntity, @Bind("relation") int relation, - @Bind("json") String json); + @Bind("json") String json, + @Bind("fromFQNHash") String fromFQNHash, + @Bind("toFQNHash") String toFQNHash); + + default void insert( + UUID fromId, UUID toId, String fromEntity, String toEntity, int relation, String json) { + insert(fromId, toId, fromEntity, toEntity, relation, json, null, null); + } @ConnectionAwareSqlUpdate( value = @@ -2421,6 +2434,32 @@ default void batchDeleteRelationships(List entityIds, String entityType) { @SqlUpdate("DELETE from entity_relationship WHERE fromId = :id or toId = :id") void deleteAllWithId(@BindUUID("id") UUID id); + @SqlUpdate( + "UPDATE entity_relationship SET fromFQNHash = :fqnHash " + + "WHERE fromId = :id AND fromFQNHash IS NULL") + void backfillFromFqnHash(@Bind("id") String id, @Bind("fqnHash") String fqnHash); + + @SqlUpdate( + "UPDATE entity_relationship SET toFQNHash = :fqnHash " + + "WHERE toId = :id AND toFQNHash IS NULL") + void backfillToFqnHash(@Bind("id") String id, @Bind("fqnHash") String fqnHash); + + @SqlUpdate( + "DELETE FROM entity_relationship WHERE fromFQNHash = :exact OR fromFQNHash LIKE :prefix") + void deleteByFromFqnHashPrefix( + @Bind("exact") String exactHash, @Bind("prefix") String prefixPattern); + + @SqlUpdate("DELETE FROM entity_relationship WHERE toFQNHash = :exact OR toFQNHash LIKE :prefix") + void deleteByToFqnHashPrefix( + @Bind("exact") String exactHash, @Bind("prefix") String prefixPattern); + + @Transaction + default void deleteAllByFqnHashPrefix(String fqnHashPrefix) { + String prefixPattern = fqnHashPrefix + ".%"; + deleteByFromFqnHashPrefix(fqnHashPrefix, prefixPattern); + deleteByToFqnHashPrefix(fqnHashPrefix, prefixPattern); + } + @ConnectionAwareSqlUpdate( value = "DELETE FROM entity_relationship " @@ -3195,6 +3234,9 @@ List> listCountThreadsByGlossaryAndTerms( @SqlQuery("select id from thread_entity where entityId = :entityId") List findByEntityId(@Bind("entityId") String entityId); + @SqlQuery("SELECT id FROM thread_entity WHERE entityId IN ()") + List findByEntityIds(@BindList("entityIds") List entityIds); + @ConnectionAwareSqlUpdate( value = "UPDATE thread_entity SET json = JSON_SET(json, '$.about', :newEntityLink)\n" @@ -6168,6 +6210,9 @@ List getUsageById( @SqlUpdate("DELETE FROM entity_usage WHERE id = :id") void delete(@BindUUID("id") UUID id); + @SqlUpdate("DELETE FROM entity_usage WHERE id IN ()") + void deleteBatch(@BindList("ids") List ids); + /** * TODO: Not sure I get what the next comment means, but tests now use mysql 8 so maybe tests can be improved here * Note not using in following percentile computation PERCENT_RANK function as unit tests use mysql5.7, and it does diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java index 205c84e54095..8106cd2169fc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java @@ -129,10 +129,14 @@ public void storeRelationships(DatabaseSchema schema) { EntityReference database = schema.getDatabase(); addRelationship( database.getId(), + database.getFullyQualifiedName(), schema.getId(), + schema.getFullyQualifiedName(), database.getType(), Entity.DATABASE_SCHEMA, - Relationship.CONTAINS); + Relationship.CONTAINS, + null, + false); } @Override diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java index 0e8c8d69e562..8e8d918ec70a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityDAO.java @@ -191,6 +191,19 @@ default void updateFqn(String oldPrefix, String newPrefix) { void updateFqnInternal( @Define("mySqlUpdate") String mySqlUpdate, @Define("postgresUpdate") String postgresUpdate); + @SqlQuery("SELECT id FROM WHERE LIKE :prefix") + List findIdsByFqnHashPrefixInternal( + @Define("table") String table, @Define("col") String col, @Bind("prefix") String prefix); + + default List findIdsByFqnHashPrefix(String fqnHashPrefix) { + if (!"fqnHash".equals(getNameHashColumn())) { + return List.of(); + } + return findIdsByFqnHashPrefixInternal(getTableName(), "fqnHash", fqnHashPrefix + ".%").stream() + .map(UUID::fromString) + .toList(); + } + @SqlQuery("SELECT json FROM
WHERE id = :id ") String findById( @Define("table") String table, @BindUUID("id") UUID id, @Define("cond") String cond); @@ -686,6 +699,16 @@ default void delete(UUID id) { } } + @SqlUpdate("DELETE FROM
WHERE id IN ()") + void deleteBatchInternal(@Define("table") String table, @BindList("ids") List ids); + + default void deleteBatch(List ids) { + if (ids == null || ids.isEmpty()) { + return; + } + deleteBatchInternal(getTableName(), ids); + } + record EntityNameColumnHashJsonPair(String nameColumnHash, String json) {} class EntityNameColumnHashJsonPairMapper implements RowMapper { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java index 2e1ab6552f44..04a6c47039a4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java @@ -422,6 +422,10 @@ public static void setLockManager(HierarchicalLockManager manager) { lockManager = manager; } + public static HierarchicalLockManager getLockManager() { + return lockManager; + } + public boolean isSupportsOwners() { return supportsOwners; } @@ -1105,7 +1109,15 @@ protected void setInheritedFields(List entities, Fields fields) { protected final void addServiceRelationship(T entity, EntityReference service) { if (service != null) { addRelationship( - service.getId(), entity.getId(), service.getType(), entityType, Relationship.CONTAINS); + service.getId(), + service.getFullyQualifiedName(), + entity.getId(), + entity.getFullyQualifiedName(), + service.getType(), + entityType, + Relationship.CONTAINS, + null, + false); } } @@ -3785,6 +3797,16 @@ protected final void cleanup(T entityInterface) { protected void entitySpecificCleanup(T entityInterface) {} + @SuppressWarnings("unchecked") + final void callPreDelete(EntityInterface entity, String deletedBy) { + preDelete((T) entity, deletedBy); + } + + @SuppressWarnings("unchecked") + final void invalidateEntity(EntityInterface entity) { + invalidate((T) entity); + } + private void invalidate(T entity) { CACHE_WITH_ID.invalidate(new ImmutablePair<>(entityType, entity.getId())); CACHE_WITH_NAME.invalidate(new ImmutablePair<>(entityType, entity.getFullyQualifiedName())); @@ -4917,17 +4939,34 @@ public final void addRelationship( Relationship relationship, String json, boolean bidirectional) { + addRelationship( + fromId, null, toId, null, fromEntity, toEntity, relationship, json, bidirectional); + } + + @Transaction + public final void addRelationship( + UUID fromId, + String fromFqn, + UUID toId, + String toFqn, + String fromEntity, + String toEntity, + Relationship relationship, + String json, + boolean bidirectional) { UUID from = fromId; UUID to = toId; + String fromHash = fromFqn != null ? FullyQualifiedName.buildHash(fromFqn) : null; + String toHash = toFqn != null ? FullyQualifiedName.buildHash(toFqn) : null; if (bidirectional && fromId.compareTo(toId) > 0) { - // For bidirectional relationship, instead of adding two row fromId -> toId and toId -> - // fromId, just add one row where fromId is alphabetically less than toId from = toId; to = fromId; + fromHash = toFqn != null ? FullyQualifiedName.buildHash(toFqn) : null; + toHash = fromFqn != null ? FullyQualifiedName.buildHash(fromFqn) : null; } daoCollection .relationshipDAO() - .insert(from, to, fromEntity, toEntity, relationship.ordinal(), json); + .insert(from, to, fromEntity, toEntity, relationship.ordinal(), json, fromHash, toHash); // Update RDF EntityRelationship entityRelationship = @@ -4940,7 +4979,6 @@ public final void addRelationship( RdfUpdater.addRelationship(entityRelationship); if (bidirectional) { - // Also add the reverse relationship to RDF EntityRelationship reverseRelationship = new EntityRelationship() .withFromId(toId) @@ -4952,6 +4990,10 @@ public final void addRelationship( } } + protected void deleteTimeSeriesByFqnPrefix(String fqnHashPrefix) { + // No-op default — override in repositories with associated time-series tables + } + @Transaction public final void bulkAddToRelationship( UUID fromId, List toId, String fromEntity, String toEntity, Relationship relationship) { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java index 96ea3e06a08f..65da83fff948 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityTimeSeriesDAO.java @@ -530,6 +530,17 @@ default void delete(String entityFQNHash, String extension) { delete(getTimeSeriesTableName(), entityFQNHash, extension); } + @SqlUpdate( + "DELETE FROM
WHERE entityFQNHash = :exactHash OR entityFQNHash LIKE :prefixHash") + void deleteByFqnHashPrefixInternal( + @Define("table") String table, + @Bind("exactHash") String exactHash, + @Bind("prefixHash") String prefixHash); + + default void deleteByFqnHashPrefix(String fqnHashPrefix) { + deleteByFqnHashPrefixInternal(getTimeSeriesTableName(), fqnHashPrefix, fqnHashPrefix + ".%"); + } + @SqlUpdate( "DELETE FROM
WHERE entityFQNHash = :entityFQNHash AND extension = :extension AND timestamp = :timestamp") void deleteAtTimestamp( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/FeedRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/FeedRepository.java index 0364eb316305..04fa428f4609 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/FeedRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/FeedRepository.java @@ -772,6 +772,22 @@ public void deleteByAbout(UUID entityId) { } } + @Transaction + public void deleteByAboutBatch(List entityIds) { + if (nullOrEmpty(entityIds)) { + return; + } + List ids = entityIds.stream().map(UUID::toString).toList(); + List threadIds = listOrEmpty(dao.feedDAO().findByEntityIds(ids)); + for (String threadId : threadIds) { + try { + deleteThreadInternal(UUID.fromString(threadId)); + } catch (Exception ex) { + // Continue deletion + } + } + } + public List getThreadsCount(String link) { List> result; EntityLink entityLink = EntityLink.parse(link); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java new file mode 100644 index 000000000000..8783395c2372 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.service.jdbi3; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.openmetadata.schema.EntityInterface; +import org.openmetadata.service.Entity; +import org.openmetadata.service.events.lifecycle.EntityLifecycleEventDispatcher; +import org.openmetadata.service.lock.HierarchicalLockManager; +import org.openmetadata.service.util.FullyQualifiedName; + +/** + * Orchestrates fast prefix-based hard deletion of an entity and all its descendants. + * + *

Uses 7 phases to atomically delete an entire subtree by FQN prefix rather than walking the + * entity tree one-by-one. This is orders of magnitude faster for large hierarchies and eliminates + * the race condition where concurrent ingestion creates orphaned entities during slow cascade + * deletion. + * + *

This service handles hard delete only. Soft delete still uses the existing tree walk in + * {@link EntityRepository} to preserve relationship data for restoration. + */ +@Slf4j +public final class PrefixDeletionService { + + private static volatile PrefixDeletionService instance; + + private final HierarchicalLockManager lockManager; + + private PrefixDeletionService(HierarchicalLockManager lockManager) { + this.lockManager = lockManager; + } + + public static void initialize(HierarchicalLockManager lockMgr) { + if (instance == null) { + synchronized (PrefixDeletionService.class) { + if (instance == null) { + instance = new PrefixDeletionService(lockMgr); + } + } + } + } + + public static PrefixDeletionService getInstance() { + return instance; + } + + /** + * Hard-deletes the given root entity and all descendants whose FQN starts with the root's FQN. + * Works at any hierarchy level (service, database, schema, etc.). + */ + public void deletePrefixHard(EntityInterface rootEntity, String deletedBy) { + String rootFqn = rootEntity.getFullyQualifiedName(); + String fqnHashPrefix = FullyQualifiedName.buildHash(rootFqn); + DeletionLock lock = acquireLock(rootEntity, deletedBy); + try { + Map> descendantsByType = collectDescendantIds(fqnHashPrefix); + List allIds = buildAllIds(rootEntity.getId(), descendantsByType); + deleteDependencyTables(rootFqn, fqnHashPrefix, allIds); + runEntityHooks(rootEntity, deletedBy, fqnHashPrefix); + deleteEntityTables(descendantsByType, rootEntity); + emitDeleteEvent(rootEntity); + } finally { + releaseLock(lock, rootEntity); + } + } + + private DeletionLock acquireLock(EntityInterface entity, String deletedBy) { + if (lockManager == null) { + return null; + } + try { + return lockManager.acquireDeletionLock(entity, deletedBy, true); + } catch (Exception e) { + LOG.warn( + "Could not acquire deletion lock for {}: {}", + entity.getFullyQualifiedName(), + e.getMessage()); + return null; + } + } + + private Map> collectDescendantIds(String fqnHashPrefix) { + Map> descendantsByType = new HashMap<>(); + for (String entityType : Entity.getEntityList()) { + try { + EntityRepository repo = Entity.getEntityRepository(entityType); + List ids = repo.getDao().findIdsByFqnHashPrefix(fqnHashPrefix); + if (!ids.isEmpty()) { + descendantsByType.put(entityType, ids); + } + } catch (Exception e) { + LOG.debug("Skipping type {} during descendant collection: {}", entityType, e.getMessage()); + } + } + return descendantsByType; + } + + private List buildAllIds(UUID rootId, Map> descendantsByType) { + List allIds = new ArrayList<>(); + allIds.add(rootId.toString()); + for (List ids : descendantsByType.values()) { + for (UUID id : ids) { + allIds.add(id.toString()); + } + } + return allIds; + } + + private void deleteDependencyTables(String rootFqn, String fqnHashPrefix, List allIds) { + CollectionDAO dao = Entity.getCollectionDAO(); + dao.relationshipDAO().deleteAllByFqnHashPrefix(fqnHashPrefix); + dao.fieldRelationshipDAO().deleteAllByPrefix(rootFqn); + dao.entityExtensionDAO().deleteAllBatch(allIds); + dao.tagUsageDAO().deleteTagLabelsByTargetPrefix(rootFqn); + dao.usageDAO().deleteBatch(allIds); + List allUuids = allIds.stream().map(UUID::fromString).toList(); + Entity.getFeedRepository().deleteByAboutBatch(allUuids); + } + + private void runEntityHooks(EntityInterface rootEntity, String deletedBy, String fqnHashPrefix) { + String rootType = rootEntity.getEntityReference().getType(); + try { + Entity.getEntityRepository(rootType).callPreDelete(rootEntity, deletedBy); + } catch (Exception e) { + LOG.warn( + "preDelete hook failed for {}: {}", rootEntity.getFullyQualifiedName(), e.getMessage()); + } + for (String entityType : Entity.getEntityList()) { + try { + Entity.getEntityRepository(entityType).deleteTimeSeriesByFqnPrefix(fqnHashPrefix); + } catch (Exception e) { + LOG.debug("deleteTimeSeriesByFqnPrefix failed for type {}: {}", entityType, e.getMessage()); + } + } + } + + private void deleteEntityTables( + Map> descendantsByType, EntityInterface rootEntity) { + for (Map.Entry> entry : descendantsByType.entrySet()) { + String entityType = entry.getKey(); + List ids = entry.getValue().stream().map(UUID::toString).toList(); + try { + Entity.getEntityRepository(entityType).getDao().deleteBatch(ids); + } catch (Exception e) { + LOG.warn( + "Failed to delete {} entities of type {}: {}", ids.size(), entityType, e.getMessage()); + } + } + String rootType = rootEntity.getEntityReference().getType(); + EntityRepository rootRepo = Entity.getEntityRepository(rootType); + rootRepo.getDao().delete(rootEntity.getId()); + rootRepo.invalidateEntity(rootEntity); + } + + private void emitDeleteEvent(EntityInterface rootEntity) { + try { + EntityLifecycleEventDispatcher.getInstance().onEntityDeleted(rootEntity, null); + } catch (Exception e) { + LOG.warn( + "Failed to emit delete event for {}: {}", + rootEntity.getFullyQualifiedName(), + e.getMessage()); + } + } + + private void releaseLock(DeletionLock lock, EntityInterface entity) { + if (lock == null || lockManager == null) { + return; + } + try { + lockManager.releaseDeletionLock(entity.getId(), entity.getEntityReference().getType()); + } catch (Exception e) { + LOG.warn( + "Failed to release deletion lock for {}: {}", + entity.getFullyQualifiedName(), + e.getMessage()); + } + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java index 81f4fa8c1ab8..bea11248d43d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java @@ -1632,13 +1632,16 @@ private void collectColumnFqns(List columns, List columnFqns) { @Override public void storeRelationships(Table table) { - // Add relationship from database to table addRelationship( table.getDatabaseSchema().getId(), + table.getDatabaseSchema().getFullyQualifiedName(), table.getId(), + table.getFullyQualifiedName(), DATABASE_SCHEMA, TABLE, - Relationship.CONTAINS); + Relationship.CONTAINS, + null, + false); } @Override diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java new file mode 100644 index 000000000000..e9dad1e54aa9 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.service.migration.mysql.v1141; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; +import org.openmetadata.service.migration.utils.v1141.MigrationUtil; + +@Slf4j +public class Migration extends MigrationProcessImpl { + + public Migration(MigrationFile migrationFile) { + super(migrationFile); + } + + @Override + @SneakyThrows + public void runDataMigration() { + try { + MigrationUtil.backfillRelationshipFqnHashes(handle); + } catch (Exception e) { + LOG.error( + "Failed to backfill FQN hashes in entity_relationship during v1141 migration. " + + "Fast prefix deletion may not work correctly for pre-existing relationships.", + e); + } + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java new file mode 100644 index 000000000000..5569edd2f1fe --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.service.migration.postgres.v1141; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; +import org.openmetadata.service.migration.utils.v1141.MigrationUtil; + +@Slf4j +public class Migration extends MigrationProcessImpl { + + public Migration(MigrationFile migrationFile) { + super(migrationFile); + } + + @Override + @SneakyThrows + public void runDataMigration() { + try { + MigrationUtil.backfillRelationshipFqnHashes(handle); + } catch (Exception e) { + LOG.error( + "Failed to backfill FQN hashes in entity_relationship during v1141 migration. " + + "Fast prefix deletion may not work correctly for pre-existing relationships.", + e); + } + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java new file mode 100644 index 000000000000..c66e7cdd729b --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.service.migration.utils.v1141; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import lombok.extern.slf4j.Slf4j; +import org.jdbi.v3.core.Handle; +import org.openmetadata.service.Entity; +import org.openmetadata.service.jdbi3.EntityRepository; + +@Slf4j +public final class MigrationUtil { + + private static final int BATCH_SIZE = 1000; + + private MigrationUtil() {} + + /** + * Backfills fromFQNHash and toFQNHash in entity_relationship for all existing rows where those + * columns are NULL. Rows are matched by fromId/toId against every entity table that uses the + * fqnHash naming column (i.e., all non-top-level entities). + */ + public static void backfillRelationshipFqnHashes(Handle handle) { + for (String entityType : Entity.getEntityList()) { + try { + backfillForEntityType(handle, entityType); + } catch (Exception e) { + LOG.warn( + "Failed to backfill FQN hashes for entity type {}: {}", entityType, e.getMessage()); + } + } + } + + private static void backfillForEntityType(Handle handle, String entityType) { + EntityRepository repo = Entity.getEntityRepository(entityType); + if (!"fqnHash".equals(repo.getDao().getNameHashColumn())) { + return; + } + String tableName = repo.getDao().getTableName(); + int offset = 0; + int processed; + do { + processed = processEntityBatch(handle, tableName, offset); + offset += processed; + } while (processed == BATCH_SIZE); + LOG.info("Backfilled FQN hashes for entity type {}: {} rows", entityType, offset); + } + + private static int processEntityBatch(Handle handle, String tableName, int offset) { + String sql = + "SELECT id, fqnHash FROM " + + tableName + + " WHERE fqnHash IS NOT NULL LIMIT :limit OFFSET :offset"; + List> rows = + handle.createQuery(sql).bind("limit", BATCH_SIZE).bind("offset", offset).mapToMap().list(); + for (Map row : rows) { + backfillRow(handle, row); + } + return rows.size(); + } + + private static void backfillRow(Handle handle, Map row) { + Map normalized = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + normalized.putAll(row); + String id = String.valueOf(normalized.get("id")); + String fqnHash = String.valueOf(normalized.get("fqnHash")); + handle + .createUpdate( + "UPDATE entity_relationship SET fromFQNHash = :fqnHash WHERE fromId = :id AND fromFQNHash IS NULL") + .bind("fqnHash", fqnHash) + .bind("id", id) + .execute(); + handle + .createUpdate( + "UPDATE entity_relationship SET toFQNHash = :fqnHash WHERE toId = :id AND toFQNHash IS NULL") + .bind("fqnHash", fqnHash) + .bind("id", id) + .execute(); + } +} diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java index bee61836e6c8..a8f3c21945d9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java @@ -73,6 +73,7 @@ import org.openmetadata.service.exception.CatalogExceptionMessage; import org.openmetadata.service.jdbi3.EntityRepository; import org.openmetadata.service.jdbi3.ListFilter; +import org.openmetadata.service.jdbi3.PrefixDeletionService; import org.openmetadata.service.limits.Limits; import org.openmetadata.service.mapper.EntityMapper; import org.openmetadata.service.monitoring.LatencyPhase; @@ -704,6 +705,43 @@ public Response deleteByIdAsync( return response; } + public Response deletePrefixHardById(UriInfo uriInfo, SecurityContext securityContext, UUID id) { + String jobId = UUID.randomUUID().toString(); + OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE); + authorizer.authorize( + securityContext, + operationContext, + getResourceContextById(id, ResourceContextInterface.Operation.DELETE)); + T entity = repository.get(uriInfo, id, repository.getFields("name"), Include.ALL, false); + String userName = securityContext.getUserPrincipal().getName(); + ExecutorService executorService = AsyncService.getInstance().getExecutorService(); + executorService.submit( + RequestLatencyContext.wrapWithContext( + () -> { + try { + PrefixDeletionService.getInstance().deletePrefixHard(entity, userName); + limits.invalidateCache(entityType); + WebsocketNotificationHandler.sendDeleteOperationCompleteNotification( + jobId, securityContext, entity); + } catch (Exception e) { + WebsocketNotificationHandler.sendDeleteOperationFailedNotification( + jobId, + securityContext, + entity, + e.getMessage() == null ? e.toString() : e.getMessage()); + } + })); + return Response.accepted() + .entity( + new DeleteEntityResponse( + jobId, + "Fast prefix deletion initiated for " + entity.getName(), + entity.getName(), + true, + true)) + .build(); + } + public Response deleteByName( UriInfo uriInfo, SecurityContext securityContext, diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java index 394a145c493f..764a7d13ef64 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java @@ -732,6 +732,30 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDatabaseServicePrefixHard", + summary = "Hard-delete a database service and all descendants by FQN prefix", + description = + "Bulk hard-delete a database service and all its databases, schemas, and tables using " + + "FQN prefix matching. This is significantly faster than recursive delete for large " + + "hierarchies and eliminates race conditions with concurrent ingestion.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse( + responseCode = "404", + description = "DatabaseService for instance {id} is not found") + }) + public Response deletePrefixHard( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the database service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( From b9eb147e7123bb6aa8e083bafb520b0f4b981c7b Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 12:44:38 +0530 Subject: [PATCH 02/29] fix: backfill entity_relationship FQN hashes for all entity types including top-level (nameHash) The original backfill only covered hierarchical entities (fqnHash column: databases, schemas, tables) but skipped top-level entities that use nameHash (services, users, teams, roles, etc.). For top-level entities, nameHash == buildHash(name) == buildHash(fqn) since FQN = name, so the nameHash IS the correct FQN hash to store in entity_relationship. Without this fix: - service->database CONTAINS rows had fromFQNHash = null for pre-existing data - user->service OWNS rows had toFQNHash = null for pre-existing data MigrationUtil now processes both fqnHash and nameHash entity types, reading whichever hash column the entity uses and updating entity_relationship accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../migration/utils/v1141/MigrationUtil.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java index c66e7cdd729b..83d3edfdd42e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java @@ -28,10 +28,17 @@ public final class MigrationUtil { private MigrationUtil() {} + // Top-level entities (services, users, teams, etc.) use "nameHash". + // For them FQN == name, so nameHash == buildHash(fqn) — the same value we need. + // Hierarchical entities (databases, schemas, tables) use "fqnHash". + // Both must be backfilled into entity_relationship so prefix deletion works for all levels. + private static final String FQNHASH_COL = "fqnHash"; + private static final String NAMEHASH_COL = "nameHash"; + /** * Backfills fromFQNHash and toFQNHash in entity_relationship for all existing rows where those - * columns are NULL. Rows are matched by fromId/toId against every entity table that uses the - * fqnHash naming column (i.e., all non-top-level entities). + * columns are NULL. Covers every registered entity type — both hierarchical entities (fqnHash + * column) and top-level entities (nameHash column, e.g. services, users, teams). */ public static void backfillRelationshipFqnHashes(Handle handle) { for (String entityType : Entity.getEntityList()) { @@ -46,37 +53,43 @@ public static void backfillRelationshipFqnHashes(Handle handle) { private static void backfillForEntityType(Handle handle, String entityType) { EntityRepository repo = Entity.getEntityRepository(entityType); - if (!"fqnHash".equals(repo.getDao().getNameHashColumn())) { + String hashCol = repo.getDao().getNameHashColumn(); + if (!FQNHASH_COL.equals(hashCol) && !NAMEHASH_COL.equals(hashCol)) { return; } String tableName = repo.getDao().getTableName(); int offset = 0; int processed; do { - processed = processEntityBatch(handle, tableName, offset); + processed = processEntityBatch(handle, tableName, hashCol, offset); offset += processed; } while (processed == BATCH_SIZE); LOG.info("Backfilled FQN hashes for entity type {}: {} rows", entityType, offset); } - private static int processEntityBatch(Handle handle, String tableName, int offset) { + private static int processEntityBatch( + Handle handle, String tableName, String hashCol, int offset) { String sql = - "SELECT id, fqnHash FROM " + "SELECT id, " + + hashCol + + " FROM " + tableName - + " WHERE fqnHash IS NOT NULL LIMIT :limit OFFSET :offset"; + + " WHERE " + + hashCol + + " IS NOT NULL LIMIT :limit OFFSET :offset"; List> rows = handle.createQuery(sql).bind("limit", BATCH_SIZE).bind("offset", offset).mapToMap().list(); for (Map row : rows) { - backfillRow(handle, row); + backfillRow(handle, row, hashCol); } return rows.size(); } - private static void backfillRow(Handle handle, Map row) { + private static void backfillRow(Handle handle, Map row, String hashCol) { Map normalized = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); normalized.putAll(row); String id = String.valueOf(normalized.get("id")); - String fqnHash = String.valueOf(normalized.get("fqnHash")); + String fqnHash = String.valueOf(normalized.get(hashCol)); handle .createUpdate( "UPDATE entity_relationship SET fromFQNHash = :fqnHash WHERE fromId = :id AND fromFQNHash IS NULL") From 5a0d7d2813554e939d1ff28f41de3fc691feae2a Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 14:15:12 +0530 Subject: [PATCH 03/29] refactor: move entity_relationship FQN hash migrations from v1.14.1 to v1.13.0 DDL (ADD COLUMN + INDEX for fromFQNHash/toFQNHash) and the Java backfill (backfillRelationshipFqnHashes) both move into the v1.13.0 migration so existing deployments upgrading to v1.13 get the columns and backfill in one shot. - v1.13.0 mysql/postgres schemaChanges.sql: append ADD COLUMN + CREATE INDEX - v1.13.0 MigrationUtil: add backfillRelationshipFqnHashes + helpers - v1.13.0 mysql/postgres Migration.java: call backfill after existing logic - v1.14.1 schemaChanges.sql: no-op (columns already added by v1.13.0) - v1.14.1 Migration.java: no-op (backfill already done by v1.13.0) - Remove v1.14.1/MigrationUtil.java (logic merged into v1.13.0) Co-Authored-By: Claude Sonnet 4.6 --- .../native/1.13.0/mysql/schemaChanges.sql | 10 ++ .../native/1.13.0/postgres/schemaChanges.sql | 10 ++ .../native/1.14.1/mysql/schemaChanges.sql | 10 +- .../native/1.14.1/postgres/schemaChanges.sql | 10 +- .../migration/mysql/v1130/Migration.java | 10 ++ .../migration/mysql/v1141/Migration.java | 17 --- .../migration/postgres/v1130/Migration.java | 10 ++ .../migration/postgres/v1141/Migration.java | 17 --- .../migration/utils/v1130/MigrationUtil.java | 72 ++++++++++++ .../migration/utils/v1141/MigrationUtil.java | 106 ------------------ 10 files changed, 114 insertions(+), 158 deletions(-) delete mode 100644 openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java diff --git a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql index ee41e5655fab..76066e198c94 100644 --- a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql @@ -129,3 +129,13 @@ SELECT ue.id, re.id, 'user', 'role', 10 FROM user_entity ue, role_entity re WHERE ue.name = 'mcpapplicationbot' AND re.name = 'ApplicationBotImpersonationRole'; + +-- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. +-- This allows deleting all relationships for an entire entity subtree in a single indexed query +-- instead of walking the tree entity-by-entity. +ALTER TABLE entity_relationship + ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, + ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; + +CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); +CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); diff --git a/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql index 87918fb2a7d1..b6d911588ec2 100644 --- a/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql @@ -150,3 +150,13 @@ FROM user_entity ue, role_entity re WHERE ue.name = 'mcpapplicationbot' AND re.name = 'ApplicationBotImpersonationRole' ON CONFLICT DO NOTHING; + +-- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. +-- This allows deleting all relationships for an entire entity subtree in a single indexed query +-- instead of walking the tree entity-by-entity. +ALTER TABLE entity_relationship + ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, + ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; + +CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash); +CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash); diff --git a/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql index 16e8c246ab13..3afbbac80252 100644 --- a/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.14.1/mysql/schemaChanges.sql @@ -1,9 +1 @@ --- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. --- This allows deleting all relationships for an entire entity subtree in a single indexed query --- instead of walking the tree entity-by-entity. -ALTER TABLE entity_relationship - ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, - ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; - -CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); -CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); +-- No schema changes in this version. diff --git a/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql index 4fdc9d342ede..3afbbac80252 100644 --- a/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.14.1/postgres/schemaChanges.sql @@ -1,9 +1 @@ --- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. --- This allows deleting all relationships for an entire entity subtree in a single indexed query --- instead of walking the tree entity-by-entity. -ALTER TABLE entity_relationship - ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, - ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; - -CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash); -CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash); +-- No schema changes in this version. diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java index d146890469f6..b887c80ee789 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java @@ -1,10 +1,12 @@ package org.openmetadata.service.migration.mysql.v1130; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.openmetadata.service.migration.api.MigrationProcessImpl; import org.openmetadata.service.migration.utils.MigrationFile; import org.openmetadata.service.migration.utils.v1130.MigrationUtil; +@Slf4j public class Migration extends MigrationProcessImpl { public Migration(MigrationFile migrationFile) { @@ -15,5 +17,13 @@ public Migration(MigrationFile migrationFile) { @SneakyThrows public void runDataMigration() { MigrationUtil.updateOwnerChartFormulas(); + try { + MigrationUtil.backfillRelationshipFqnHashes(handle); + } catch (Exception e) { + LOG.error( + "Failed to backfill FQN hashes in entity_relationship during v1130 migration. " + + "Fast prefix deletion may not work correctly for pre-existing relationships.", + e); + } } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java index e9dad1e54aa9..7d381de87393 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1141/Migration.java @@ -13,29 +13,12 @@ package org.openmetadata.service.migration.mysql.v1141; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; import org.openmetadata.service.migration.api.MigrationProcessImpl; import org.openmetadata.service.migration.utils.MigrationFile; -import org.openmetadata.service.migration.utils.v1141.MigrationUtil; -@Slf4j public class Migration extends MigrationProcessImpl { public Migration(MigrationFile migrationFile) { super(migrationFile); } - - @Override - @SneakyThrows - public void runDataMigration() { - try { - MigrationUtil.backfillRelationshipFqnHashes(handle); - } catch (Exception e) { - LOG.error( - "Failed to backfill FQN hashes in entity_relationship during v1141 migration. " - + "Fast prefix deletion may not work correctly for pre-existing relationships.", - e); - } - } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java index 909ea509319d..570eda6bf4ab 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java @@ -1,10 +1,12 @@ package org.openmetadata.service.migration.postgres.v1130; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.openmetadata.service.migration.api.MigrationProcessImpl; import org.openmetadata.service.migration.utils.MigrationFile; import org.openmetadata.service.migration.utils.v1130.MigrationUtil; +@Slf4j public class Migration extends MigrationProcessImpl { public Migration(MigrationFile migrationFile) { @@ -15,5 +17,13 @@ public Migration(MigrationFile migrationFile) { @SneakyThrows public void runDataMigration() { MigrationUtil.updateOwnerChartFormulas(); + try { + MigrationUtil.backfillRelationshipFqnHashes(handle); + } catch (Exception e) { + LOG.error( + "Failed to backfill FQN hashes in entity_relationship during v1130 migration. " + + "Fast prefix deletion may not work correctly for pre-existing relationships.", + e); + } } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java index 5569edd2f1fe..e0c6d4f761f8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1141/Migration.java @@ -13,29 +13,12 @@ package org.openmetadata.service.migration.postgres.v1141; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; import org.openmetadata.service.migration.api.MigrationProcessImpl; import org.openmetadata.service.migration.utils.MigrationFile; -import org.openmetadata.service.migration.utils.v1141.MigrationUtil; -@Slf4j public class Migration extends MigrationProcessImpl { public Migration(MigrationFile migrationFile) { super(migrationFile); } - - @Override - @SneakyThrows - public void runDataMigration() { - try { - MigrationUtil.backfillRelationshipFqnHashes(handle); - } catch (Exception e) { - LOG.error( - "Failed to backfill FQN hashes in entity_relationship during v1141 migration. " - + "Fast prefix deletion may not work correctly for pre-existing relationships.", - e); - } - } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java index f5005e76f4b3..a7f2d8fc8327 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java @@ -1,17 +1,89 @@ package org.openmetadata.service.migration.utils.v1130; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import lombok.extern.slf4j.Slf4j; +import org.jdbi.v3.core.Handle; import org.openmetadata.schema.dataInsight.custom.DataInsightCustomChart; +import org.openmetadata.service.Entity; import org.openmetadata.service.jdbi3.DataInsightSystemChartRepository; +import org.openmetadata.service.jdbi3.EntityRepository; import org.openmetadata.service.util.EntityUtil; @Slf4j public class MigrationUtil { private MigrationUtil() {} + private static final int BATCH_SIZE = 1000; + private static final String FQNHASH_COL = "fqnHash"; + private static final String NAMEHASH_COL = "nameHash"; + private static final String OLD_FIELD = "owners.name.keyword"; private static final String NEW_FIELD = "ownerName"; + public static void backfillRelationshipFqnHashes(Handle handle) { + for (String entityType : Entity.getEntityList()) { + try { + backfillForEntityType(handle, entityType); + } catch (Exception e) { + LOG.warn("Failed to backfill FQN hashes for entity type {}: {}", entityType, e.getMessage()); + } + } + } + + private static void backfillForEntityType(Handle handle, String entityType) { + EntityRepository repo = Entity.getEntityRepository(entityType); + String hashCol = repo.getDao().getNameHashColumn(); + if (!FQNHASH_COL.equals(hashCol) && !NAMEHASH_COL.equals(hashCol)) { + return; + } + String tableName = repo.getDao().getTableName(); + int offset = 0; + int processed; + do { + processed = processEntityBatch(handle, tableName, hashCol, offset); + offset += processed; + } while (processed == BATCH_SIZE); + LOG.info("Backfilled FQN hashes for entity type {}: {} rows", entityType, offset); + } + + private static int processEntityBatch(Handle handle, String tableName, String hashCol, int offset) { + String sql = + "SELECT id, " + + hashCol + + " FROM " + + tableName + + " WHERE " + + hashCol + + " IS NOT NULL LIMIT :limit OFFSET :offset"; + List> rows = + handle.createQuery(sql).bind("limit", BATCH_SIZE).bind("offset", offset).mapToMap().list(); + for (Map row : rows) { + backfillRow(handle, row, hashCol); + } + return rows.size(); + } + + private static void backfillRow(Handle handle, Map row, String hashCol) { + Map normalized = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + normalized.putAll(row); + String id = String.valueOf(normalized.get("id")); + String fqnHash = String.valueOf(normalized.get(hashCol)); + handle + .createUpdate( + "UPDATE entity_relationship SET fromFQNHash = :fqnHash WHERE fromId = :id AND fromFQNHash IS NULL") + .bind("fqnHash", fqnHash) + .bind("id", id) + .execute(); + handle + .createUpdate( + "UPDATE entity_relationship SET toFQNHash = :fqnHash WHERE toId = :id AND toFQNHash IS NULL") + .bind("fqnHash", fqnHash) + .bind("id", id) + .execute(); + } + public static void updateOwnerChartFormulas() { DataInsightSystemChartRepository repository = new DataInsightSystemChartRepository(); String[] chartNames = { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java deleted file mode 100644 index 83d3edfdd42e..000000000000 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1141/MigrationUtil.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2021 Collate - * Licensed 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 - * http://www.apache.org/licenses/LICENSE-2.0 - * 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.openmetadata.service.migration.utils.v1141; - -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import lombok.extern.slf4j.Slf4j; -import org.jdbi.v3.core.Handle; -import org.openmetadata.service.Entity; -import org.openmetadata.service.jdbi3.EntityRepository; - -@Slf4j -public final class MigrationUtil { - - private static final int BATCH_SIZE = 1000; - - private MigrationUtil() {} - - // Top-level entities (services, users, teams, etc.) use "nameHash". - // For them FQN == name, so nameHash == buildHash(fqn) — the same value we need. - // Hierarchical entities (databases, schemas, tables) use "fqnHash". - // Both must be backfilled into entity_relationship so prefix deletion works for all levels. - private static final String FQNHASH_COL = "fqnHash"; - private static final String NAMEHASH_COL = "nameHash"; - - /** - * Backfills fromFQNHash and toFQNHash in entity_relationship for all existing rows where those - * columns are NULL. Covers every registered entity type — both hierarchical entities (fqnHash - * column) and top-level entities (nameHash column, e.g. services, users, teams). - */ - public static void backfillRelationshipFqnHashes(Handle handle) { - for (String entityType : Entity.getEntityList()) { - try { - backfillForEntityType(handle, entityType); - } catch (Exception e) { - LOG.warn( - "Failed to backfill FQN hashes for entity type {}: {}", entityType, e.getMessage()); - } - } - } - - private static void backfillForEntityType(Handle handle, String entityType) { - EntityRepository repo = Entity.getEntityRepository(entityType); - String hashCol = repo.getDao().getNameHashColumn(); - if (!FQNHASH_COL.equals(hashCol) && !NAMEHASH_COL.equals(hashCol)) { - return; - } - String tableName = repo.getDao().getTableName(); - int offset = 0; - int processed; - do { - processed = processEntityBatch(handle, tableName, hashCol, offset); - offset += processed; - } while (processed == BATCH_SIZE); - LOG.info("Backfilled FQN hashes for entity type {}: {} rows", entityType, offset); - } - - private static int processEntityBatch( - Handle handle, String tableName, String hashCol, int offset) { - String sql = - "SELECT id, " - + hashCol - + " FROM " - + tableName - + " WHERE " - + hashCol - + " IS NOT NULL LIMIT :limit OFFSET :offset"; - List> rows = - handle.createQuery(sql).bind("limit", BATCH_SIZE).bind("offset", offset).mapToMap().list(); - for (Map row : rows) { - backfillRow(handle, row, hashCol); - } - return rows.size(); - } - - private static void backfillRow(Handle handle, Map row, String hashCol) { - Map normalized = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - normalized.putAll(row); - String id = String.valueOf(normalized.get("id")); - String fqnHash = String.valueOf(normalized.get(hashCol)); - handle - .createUpdate( - "UPDATE entity_relationship SET fromFQNHash = :fqnHash WHERE fromId = :id AND fromFQNHash IS NULL") - .bind("fqnHash", fqnHash) - .bind("id", id) - .execute(); - handle - .createUpdate( - "UPDATE entity_relationship SET toFQNHash = :fqnHash WHERE toId = :id AND toFQNHash IS NULL") - .bind("fqnHash", fqnHash) - .bind("id", id) - .execute(); - } -} From 4c2c690c42cc01f4b51bdaa5b2eb1595b9b53d69 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 14:54:41 +0530 Subject: [PATCH 04/29] fix: replace paginated backfill with direct correlated-subquery UPDATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old LIMIT/OFFSET pagination over entity tables had no ORDER BY, which caused non-deterministic row ordering between batches — rows near page boundaries were silently skipped, requiring multiple retriggers to converge. New approach: one UPDATE per entity type per direction using a correlated subquery (CAST-based for MySQL/PostgreSQL compatibility). No pagination, no skipped rows, idempotent on re-run. UPDATE entity_relationship SET fromFQNHash = (SELECT CAST(t.fqnHash AS CHAR(768)) FROM

t WHERE CAST(t.id AS CHAR(36)) = entity_relationship.fromId) WHERE fromEntity = :entityType AND fromFQNHash IS NULL Co-Authored-By: Claude Sonnet 4.6 --- .../migration/utils/v1130/MigrationUtil.java | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java index a7f2d8fc8327..f0bb04becc53 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java @@ -1,8 +1,5 @@ package org.openmetadata.service.migration.utils.v1130; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.core.Handle; import org.openmetadata.schema.dataInsight.custom.DataInsightCustomChart; @@ -15,13 +12,17 @@ public class MigrationUtil { private MigrationUtil() {} - private static final int BATCH_SIZE = 1000; private static final String FQNHASH_COL = "fqnHash"; private static final String NAMEHASH_COL = "nameHash"; private static final String OLD_FIELD = "owners.name.keyword"; private static final String NEW_FIELD = "ownerName"; + /** + * Backfills fromFQNHash and toFQNHash in entity_relationship for all registered entity types. + * Uses a direct correlated-subquery UPDATE (one per direction per entity type) to avoid + * LIMIT/OFFSET pagination ordering bugs that could silently skip rows. + */ public static void backfillRelationshipFqnHashes(Handle handle) { for (String entityType : Entity.getEntityList()) { try { @@ -39,48 +40,42 @@ private static void backfillForEntityType(Handle handle, String entityType) { return; } String tableName = repo.getDao().getTableName(); - int offset = 0; - int processed; - do { - processed = processEntityBatch(handle, tableName, hashCol, offset); - offset += processed; - } while (processed == BATCH_SIZE); - LOG.info("Backfilled FQN hashes for entity type {}: {} rows", entityType, offset); - } - - private static int processEntityBatch(Handle handle, String tableName, String hashCol, int offset) { - String sql = - "SELECT id, " - + hashCol - + " FROM " - + tableName - + " WHERE " - + hashCol - + " IS NOT NULL LIMIT :limit OFFSET :offset"; - List> rows = - handle.createQuery(sql).bind("limit", BATCH_SIZE).bind("offset", offset).mapToMap().list(); - for (Map row : rows) { - backfillRow(handle, row, hashCol); + int fromUpdated = updateFromHashes(handle, entityType, tableName, hashCol); + int toUpdated = updateToHashes(handle, entityType, tableName, hashCol); + if (fromUpdated + toUpdated > 0) { + LOG.info( + "Backfilled FQN hashes for entity type {}: {} fromFQNHash, {} toFQNHash", + entityType, fromUpdated, toUpdated); } - return rows.size(); } - private static void backfillRow(Handle handle, Map row, String hashCol) { - Map normalized = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - normalized.putAll(row); - String id = String.valueOf(normalized.get("id")); - String fqnHash = String.valueOf(normalized.get(hashCol)); - handle + private static int updateFromHashes( + Handle handle, String entityType, String tableName, String hashCol) { + return handle .createUpdate( - "UPDATE entity_relationship SET fromFQNHash = :fqnHash WHERE fromId = :id AND fromFQNHash IS NULL") - .bind("fqnHash", fqnHash) - .bind("id", id) + "UPDATE entity_relationship SET fromFQNHash = (" + + "SELECT CAST(t." + + hashCol + + " AS CHAR(768)) FROM " + + tableName + + " t WHERE CAST(t.id AS CHAR(36)) = entity_relationship.fromId" + + ") WHERE fromEntity = :entityType AND fromFQNHash IS NULL") + .bind("entityType", entityType) .execute(); - handle + } + + private static int updateToHashes( + Handle handle, String entityType, String tableName, String hashCol) { + return handle .createUpdate( - "UPDATE entity_relationship SET toFQNHash = :fqnHash WHERE toId = :id AND toFQNHash IS NULL") - .bind("fqnHash", fqnHash) - .bind("id", id) + "UPDATE entity_relationship SET toFQNHash = (" + + "SELECT CAST(t." + + hashCol + + " AS CHAR(768)) FROM " + + tableName + + " t WHERE CAST(t.id AS CHAR(36)) = entity_relationship.toId" + + ") WHERE toEntity = :entityType AND toFQNHash IS NULL") + .bind("entityType", entityType) .execute(); } From 6b266b8c6fb01f29318ce2c4d8ed20cbe47e347e Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 15:28:21 +0530 Subject: [PATCH 05/29] test: add unit tests for v1.13.0 FQN hash backfill migration Adds 11 unit tests covering the correlated-subquery backfill logic introduced in MigrationUtil.backfillRelationshipFqnHashes(): - MigrationUtilTest: 7 tests verifying empty entity list no-ops, fqnHash/nameHash entity updates, unrecognised hash column skip, time-series entity exception swallowing, multi-entity failure isolation, and correct CAST SQL generation - mysql/v1130/MigrationTest: 2 tests verifying runDataMigration delegates to backfillRelationshipFqnHashes and swallows errors - postgres/v1130/MigrationTest: same 2 tests for Postgres variant Co-Authored-By: Claude Sonnet 4.6 --- .../migration/mysql/v1130/MigrationTest.java | 55 +++++++ .../postgres/v1130/MigrationTest.java | 55 +++++++ .../utils/v1130/MigrationUtilTest.java | 150 ++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 openmetadata-service/src/test/java/org/openmetadata/service/migration/mysql/v1130/MigrationTest.java create mode 100644 openmetadata-service/src/test/java/org/openmetadata/service/migration/postgres/v1130/MigrationTest.java create mode 100644 openmetadata-service/src/test/java/org/openmetadata/service/migration/utils/v1130/MigrationUtilTest.java diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/migration/mysql/v1130/MigrationTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/migration/mysql/v1130/MigrationTest.java new file mode 100644 index 000000000000..af0b0c89a89b --- /dev/null +++ b/openmetadata-service/src/test/java/org/openmetadata/service/migration/mysql/v1130/MigrationTest.java @@ -0,0 +1,55 @@ +package org.openmetadata.service.migration.mysql.v1130; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import java.lang.reflect.Field; +import org.jdbi.v3.core.Handle; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; +import org.openmetadata.service.migration.utils.v1130.MigrationUtil; + +class MigrationTest { + + private Migration createMigrationWithHandle(Handle handle) throws Exception { + MigrationFile migrationFile = mock(MigrationFile.class); + Migration migration = new Migration(migrationFile); + Field handleField = MigrationProcessImpl.class.getDeclaredField("handle"); + handleField.setAccessible(true); + handleField.set(migration, handle); + return migration; + } + + @Test + void runDataMigrationCallsBackfillRelationshipFqnHashes() throws Exception { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + Migration migration = createMigrationWithHandle(handle); + + try (MockedStatic util = mockStatic(MigrationUtil.class)) { + util.when(MigrationUtil::updateOwnerChartFormulas).then(inv -> null); + util.when(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)).then(inv -> null); + + migration.runDataMigration(); + + util.verify(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + } + } + + @Test + void runDataMigrationDoesNotThrowWhenBackfillFails() throws Exception { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + Migration migration = createMigrationWithHandle(handle); + + try (MockedStatic util = mockStatic(MigrationUtil.class)) { + util.when(MigrationUtil::updateOwnerChartFormulas).then(inv -> null); + util.when(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)) + .thenThrow(new RuntimeException("DB error")); + + assertDoesNotThrow(migration::runDataMigration); + } + } +} diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/migration/postgres/v1130/MigrationTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/migration/postgres/v1130/MigrationTest.java new file mode 100644 index 000000000000..04207aec3361 --- /dev/null +++ b/openmetadata-service/src/test/java/org/openmetadata/service/migration/postgres/v1130/MigrationTest.java @@ -0,0 +1,55 @@ +package org.openmetadata.service.migration.postgres.v1130; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import java.lang.reflect.Field; +import org.jdbi.v3.core.Handle; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.openmetadata.service.migration.api.MigrationProcessImpl; +import org.openmetadata.service.migration.utils.MigrationFile; +import org.openmetadata.service.migration.utils.v1130.MigrationUtil; + +class MigrationTest { + + private Migration createMigrationWithHandle(Handle handle) throws Exception { + MigrationFile migrationFile = mock(MigrationFile.class); + Migration migration = new Migration(migrationFile); + Field handleField = MigrationProcessImpl.class.getDeclaredField("handle"); + handleField.setAccessible(true); + handleField.set(migration, handle); + return migration; + } + + @Test + void runDataMigrationCallsBackfillRelationshipFqnHashes() throws Exception { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + Migration migration = createMigrationWithHandle(handle); + + try (MockedStatic util = mockStatic(MigrationUtil.class)) { + util.when(MigrationUtil::updateOwnerChartFormulas).then(inv -> null); + util.when(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)).then(inv -> null); + + migration.runDataMigration(); + + util.verify(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + } + } + + @Test + void runDataMigrationDoesNotThrowWhenBackfillFails() throws Exception { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + Migration migration = createMigrationWithHandle(handle); + + try (MockedStatic util = mockStatic(MigrationUtil.class)) { + util.when(MigrationUtil::updateOwnerChartFormulas).then(inv -> null); + util.when(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)) + .thenThrow(new RuntimeException("DB error")); + + assertDoesNotThrow(migration::runDataMigration); + } + } +} diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/migration/utils/v1130/MigrationUtilTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/migration/utils/v1130/MigrationUtilTest.java new file mode 100644 index 000000000000..06aee6ae7281 --- /dev/null +++ b/openmetadata-service/src/test/java/org/openmetadata/service/migration/utils/v1130/MigrationUtilTest.java @@ -0,0 +1,150 @@ +package org.openmetadata.service.migration.utils.v1130; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Set; +import org.jdbi.v3.core.Handle; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.openmetadata.service.Entity; +import org.openmetadata.service.jdbi3.EntityDAO; +import org.openmetadata.service.jdbi3.EntityRepository; + +class MigrationUtilTest { + + private Handle handleWithUpdateReturning(int rowCount) { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + when(handle.createUpdate(any()).bind(anyString(), anyString()).execute()).thenReturn(rowCount); + return handle; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private EntityRepository repoWithHashCol(String tableName, String hashCol) { + EntityDAO dao = mock(EntityDAO.class); + when(dao.getTableName()).thenReturn(tableName); + when(dao.getNameHashColumn()).thenReturn(hashCol); + EntityRepository repo = mock(EntityRepository.class); + doReturn(dao).when(repo).getDao(); + return repo; + } + + @Test + void backfillDoesNothingWhenEntityListIsEmpty() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of()); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + verify(handle, never()).createUpdate(any()); + } + } + + @Test + void backfillIssuesFromAndToUpdatesForFqnHashEntity() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + EntityRepository repo = repoWithHashCol("table_entity", "fqnHash"); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("table")); + entity.when(() -> Entity.getEntityRepository("table")).thenReturn(repo); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + + verify(handle).createUpdate(contains("fromFQNHash")); + verify(handle).createUpdate(contains("toFQNHash")); + } + } + + @Test + void backfillIssuesFromAndToUpdatesForNameHashEntity() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + EntityRepository repo = repoWithHashCol("user_entity", "nameHash"); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("user")); + entity.when(() -> Entity.getEntityRepository("user")).thenReturn(repo); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + + verify(handle).createUpdate(contains("fromFQNHash")); + verify(handle).createUpdate(contains("toFQNHash")); + } + } + + @Test + void backfillSkipsEntityTypeWithUnrecognisedHashColumn() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + EntityRepository repo = repoWithHashCol("some_entity", "otherHash"); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("someEntity")); + entity.when(() -> Entity.getEntityRepository("someEntity")).thenReturn(repo); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + + verify(handle, never()).createUpdate(any()); + } + } + + @Test + void backfillSkipsTimeSeriesEntityWhenRepositoryNotFound() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("testCaseResolutionStatus")); + entity + .when(() -> Entity.getEntityRepository("testCaseResolutionStatus")) + .thenThrow(new RuntimeException("not a regular entity")); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + + verify(handle, never()).createUpdate(any()); + } + } + + @Test + void backfillContinuesToNextEntityWhenOneEntityUpdateFails() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + when(handle.createUpdate(any()).bind(anyString(), anyString()).execute()) + .thenThrow(new RuntimeException("DB error")); + + EntityRepository tableRepo = repoWithHashCol("table_entity", "fqnHash"); + EntityRepository userRepo = repoWithHashCol("user_entity", "nameHash"); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("table", "user")); + entity.when(() -> Entity.getEntityRepository("table")).thenReturn(tableRepo); + entity.when(() -> Entity.getEntityRepository("user")).thenReturn(userRepo); + + assertDoesNotThrow(() -> MigrationUtil.backfillRelationshipFqnHashes(handle)); + } + } + + @Test + void backfillSqlContainsCorrelatedSubqueryWithCast() { + Handle handle = mock(Handle.class, RETURNS_DEEP_STUBS); + EntityRepository repo = repoWithHashCol("table_entity", "fqnHash"); + + try (MockedStatic entity = mockStatic(Entity.class)) { + entity.when(Entity::getEntityList).thenReturn(Set.of("table")); + entity.when(() -> Entity.getEntityRepository("table")).thenReturn(repo); + + MigrationUtil.backfillRelationshipFqnHashes(handle); + + verify(handle, times(2)).createUpdate(contains("CAST(t.id AS CHAR(36))")); + verify(handle, times(2)).createUpdate(contains("CAST(t.fqnHash AS CHAR(768))")); + } + } +} From 40583bc9d383066bd9f469075e72b3d327bb174f Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 15:36:42 +0530 Subject: [PATCH 06/29] test: add integration and benchmark tests for FQN prefix deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PrefixDeletionIT - three integration tests verifying the DELETE /v1/services/databaseServices/prefix/{id} endpoint: - single service with 5 tables is fully erased (service, database, schema, all tables return 404 after deletion) - service with 2×2 databases/schemas and 12 tables all erased - empty service (no databases) is deleted cleanly PrefixDeletionBenchmarkIT - disabled manual benchmark tagged @benchmark that creates equal hierarchies and times: - old: DELETE /{id}?hardDelete=true&recursive=true - new: DELETE /prefix/{id} Logs speedup factor; configurable table count via -Dtest.scenario=N. Co-Authored-By: Claude Sonnet 4.6 --- .../it/tests/PrefixDeletionBenchmarkIT.java | 150 ++++++++++++++++++ .../it/tests/PrefixDeletionIT.java | 137 ++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java create mode 100644 openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java new file mode 100644 index 000000000000..14acd008de37 --- /dev/null +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java @@ -0,0 +1,150 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.it.tests; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openmetadata.it.factories.DatabaseSchemaTestFactory; +import org.openmetadata.it.factories.DatabaseServiceTestFactory; +import org.openmetadata.it.factories.DatabaseTestFactory; +import org.openmetadata.it.factories.TableTestFactory; +import org.openmetadata.it.util.SdkClients; +import org.openmetadata.it.util.TestNamespace; +import org.openmetadata.it.util.TestNamespaceExtension; +import org.openmetadata.schema.entity.data.Database; +import org.openmetadata.schema.entity.data.DatabaseSchema; +import org.openmetadata.schema.entity.services.DatabaseService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Benchmark comparing old recursive hard delete vs new FQN prefix hard delete. + * + *

Run manually against a local stack with a pre-populated database: + * + *

+ *   mvn verify -pl openmetadata-integration-tests \
+ *     -Dgroups=benchmark \
+ *     -Dit.test=PrefixDeletionBenchmarkIT \
+ *     -Dtest.scenario=100   # tables per schema (default: 50)
+ * 
+ * + *

Results are printed to the log. The prefix approach should be 3-10x faster for large + * hierarchies because it issues one bulk DELETE per table vs one round-trip per entity. + */ +@Tag("benchmark") +@Disabled("Manual benchmark — run explicitly against a local mysql/postgres stack") +@ExtendWith(TestNamespaceExtension.class) +class PrefixDeletionBenchmarkIT { + + private static final Logger LOG = LoggerFactory.getLogger(PrefixDeletionBenchmarkIT.class); + + private static final int DATABASES_PER_SERVICE = 2; + private static final int SCHEMAS_PER_DATABASE = 2; + private static final int TABLES_PER_SCHEMA = + Integer.getInteger("test.scenario", 50); + + @BeforeAll + static void setup() { + SdkClients.adminClient(); + } + + @Test + void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throws Exception { + int totalTables = DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; + LOG.info( + "Benchmark: {} databases × {} schemas × {} tables = {} total tables per service", + DATABASES_PER_SERVICE, SCHEMAS_PER_DATABASE, TABLES_PER_SCHEMA, totalTables); + + DatabaseService oldService = buildHierarchy(ns, "old"); + long oldMs = timeOldDelete(oldService); + + DatabaseService newService = buildHierarchy(ns, "new"); + long newMs = timeNewDelete(newService); + + double speedup = (double) oldMs / Math.max(newMs, 1); + LOG.info("=== Deletion Benchmark Results ({} tables per service) ===", totalTables); + LOG.info(" Old recursive hard delete : {} ms", oldMs); + LOG.info(" New FQN prefix hard delete: {} ms", newMs); + LOG.info(" Speedup : {:.2f}x", speedup); + } + + private DatabaseService buildHierarchy(TestNamespace ns, String tag) { + DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); + LOG.info("[{}] Creating hierarchy under service: {}", tag, service.getName()); + + for (int d = 0; d < DATABASES_PER_SERVICE; d++) { + Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + for (int s = 0; s < SCHEMAS_PER_DATABASE; s++) { + DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + for (int t = 0; t < TABLES_PER_SCHEMA; t++) { + TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), tag + "t" + d + s + t); + } + } + } + + LOG.info("[{}] Hierarchy created.", tag); + return service; + } + + private long timeOldDelete(DatabaseService service) throws Exception { + LOG.info("Starting OLD recursive hard delete for service {}", service.getName()); + long start = System.currentTimeMillis(); + + String url = SdkClients.getServerUrl() + + "/v1/services/databaseServices/" + service.getId() + + "?hardDelete=true&recursive=true"; + sendDelete(url); + + long elapsed = System.currentTimeMillis() - start; + LOG.info("OLD recursive hard delete completed in {} ms", elapsed); + return elapsed; + } + + private long timeNewDelete(DatabaseService service) throws Exception { + LOG.info("Starting NEW FQN prefix hard delete for service {}", service.getName()); + long start = System.currentTimeMillis(); + + String url = SdkClients.getServerUrl() + + "/v1/services/databaseServices/prefix/" + service.getId(); + sendDelete(url); + + long elapsed = System.currentTimeMillis() - start; + LOG.info("NEW FQN prefix hard delete completed in {} ms", elapsed); + return elapsed; + } + + private void sendDelete(String url) throws Exception { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Authorization", "Bearer " + SdkClients.getAdminToken()) + .DELETE() + .build(); + HttpResponse response = + HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() >= 300) { + throw new RuntimeException( + "Delete failed with status " + response.statusCode() + ": " + response.body()); + } + } +} diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java new file mode 100644 index 000000000000..bf42a233028f --- /dev/null +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java @@ -0,0 +1,137 @@ +/* + * Copyright 2021 Collate + * Licensed 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * 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.openmetadata.it.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openmetadata.it.factories.DatabaseSchemaTestFactory; +import org.openmetadata.it.factories.DatabaseServiceTestFactory; +import org.openmetadata.it.factories.DatabaseTestFactory; +import org.openmetadata.it.factories.TableTestFactory; +import org.openmetadata.it.util.SdkClients; +import org.openmetadata.it.util.TestNamespace; +import org.openmetadata.it.util.TestNamespaceExtension; +import org.openmetadata.schema.entity.data.Database; +import org.openmetadata.schema.entity.data.DatabaseSchema; +import org.openmetadata.schema.entity.data.Table; +import org.openmetadata.schema.entity.services.DatabaseService; + +/** + * Integration tests for the FQN prefix-based hard deletion endpoint. + * + *

Verifies that {@code DELETE /v1/services/databaseServices/prefix/{id}} correctly removes the + * root service and all descendant databases, schemas, and tables in a single bulk operation. + */ +@ExtendWith(TestNamespaceExtension.class) +public class PrefixDeletionIT { + + @BeforeAll + static void setup() { + SdkClients.adminClient(); + } + + @Test + void prefixDelete_removesServiceAndAllDescendants(TestNamespace ns) throws Exception { + DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); + Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + + List tableIds = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Table table = TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + i); + tableIds.add(table.getId()); + } + + prefixDeleteService(service.getId()); + + assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); + assertEntityGone(() -> SdkClients.adminClient().databases().get(database.getId().toString()), "database"); + assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(schema.getId().toString()), "schema"); + for (UUID tableId : tableIds) { + assertEntityGone(() -> SdkClients.adminClient().tables().get(tableId.toString()), "table"); + } + } + + @Test + void prefixDelete_serviceWithMultipleDatabasesAndSchemas(TestNamespace ns) throws Exception { + DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); + + List allDescendantIds = new ArrayList<>(); + for (int d = 0; d < 2; d++) { + Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + allDescendantIds.add(database.getId()); + for (int s = 0; s < 2; s++) { + DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + allDescendantIds.add(schema.getId()); + for (int t = 0; t < 3; t++) { + Table table = TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + d + s + t); + allDescendantIds.add(table.getId()); + } + } + } + + prefixDeleteService(service.getId()); + + assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); + for (UUID id : allDescendantIds) { + final UUID finalId = id; + assertThrows(Exception.class, () -> SdkClients.adminClient().tables().get(finalId.toString())); + } + } + + @Test + void prefixDelete_emptyService_deletesJustService(TestNamespace ns) throws Exception { + DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); + assertNotNull(service.getId()); + + prefixDeleteService(service.getId()); + + assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); + } + + private void prefixDeleteService(UUID serviceId) throws Exception { + String url = SdkClients.getServerUrl() + "/v1/services/databaseServices/prefix/" + serviceId; + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Authorization", "Bearer " + SdkClients.getAdminToken()) + .DELETE() + .build(); + + HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals(202, response.statusCode(), + "Expected 202 Accepted from prefix delete, got " + response.statusCode() + ": " + response.body()); + } + + private void assertEntityGone(ThrowingRunnable action, String entityType) { + assertThrows(Exception.class, action::run, + entityType + " should be gone after prefix deletion but was still retrievable"); + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } +} From 7fe921243952d76165b5e7e2edcdd0c5a677a7af Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 15:57:53 +0530 Subject: [PATCH 07/29] feat: expose prefix hard delete at database and schema hierarchy levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously only DatabaseService had DELETE /prefix/{id}. Added the same endpoint to DatabaseResource and DatabaseSchemaResource so callers can bulk-hard-delete at any level of the service hierarchy: DELETE /v1/services/databaseServices/prefix/{id} (existing) DELETE /v1/databases/prefix/{id} (new) DELETE /v1/databaseSchemas/prefix/{id} (new) All three delegate to EntityResource.deletePrefixHardById() which runs PrefixDeletionService.deletePrefixHard() — same FQN hash prefix logic, same async execution, same auth checks. Updated PrefixDeletionIT with two new integration tests that verify the sibling-isolation guarantee: deleting a database leaves sibling databases intact, and deleting a schema leaves sibling schemas and their tables intact. Co-Authored-By: Claude Sonnet 4.6 --- .../it/tests/PrefixDeletionIT.java | 84 +++++++++++++++---- .../resources/databases/DatabaseResource.java | 21 +++++ .../databases/DatabaseSchemaResource.java | 23 +++++ 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java index bf42a233028f..8c72c2fceb49 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java @@ -40,10 +40,15 @@ import org.openmetadata.schema.entity.services.DatabaseService; /** - * Integration tests for the FQN prefix-based hard deletion endpoint. + * Integration tests for the FQN prefix-based hard deletion endpoints at each hierarchy level: * - *

Verifies that {@code DELETE /v1/services/databaseServices/prefix/{id}} correctly removes the - * root service and all descendant databases, schemas, and tables in a single bulk operation. + *

    + *
  • {@code DELETE /v1/services/databaseServices/prefix/{id}} — deletes service + all descendants + *
  • {@code DELETE /v1/databases/prefix/{id}} — deletes database + schemas + tables, leaving + * sibling databases intact + *
  • {@code DELETE /v1/databaseSchemas/prefix/{id}} — deletes schema + tables, leaving sibling + * schemas intact + *
*/ @ExtendWith(TestNamespaceExtension.class) public class PrefixDeletionIT { @@ -53,8 +58,10 @@ static void setup() { SdkClients.adminClient(); } + // ── Service-level ──────────────────────────────────────────────────────────── + @Test - void prefixDelete_removesServiceAndAllDescendants(TestNamespace ns) throws Exception { + void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); @@ -65,7 +72,7 @@ void prefixDelete_removesServiceAndAllDescendants(TestNamespace ns) throws Excep tableIds.add(table.getId()); } - prefixDeleteService(service.getId()); + prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); assertEntityGone(() -> SdkClients.adminClient().databases().get(database.getId().toString()), "database"); @@ -76,7 +83,7 @@ void prefixDelete_removesServiceAndAllDescendants(TestNamespace ns) throws Excep } @Test - void prefixDelete_serviceWithMultipleDatabasesAndSchemas(TestNamespace ns) throws Exception { + void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); List allDescendantIds = new ArrayList<>(); @@ -93,7 +100,7 @@ void prefixDelete_serviceWithMultipleDatabasesAndSchemas(TestNamespace ns) throw } } - prefixDeleteService(service.getId()); + prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); for (UUID id : allDescendantIds) { @@ -102,18 +109,66 @@ void prefixDelete_serviceWithMultipleDatabasesAndSchemas(TestNamespace ns) throw } } + // ── Database-level ──────────────────────────────────────────────────────────── + @Test - void prefixDelete_emptyService_deletesJustService(TestNamespace ns) throws Exception { + void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIntact(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - assertNotNull(service.getId()); - prefixDeleteService(service.getId()); + Database targetDb = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + DatabaseSchema targetSchema = DatabaseSchemaTestFactory.create(ns, targetDb.getFullyQualifiedName()); + Table targetTable = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt"); - assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); + Database siblingDb = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.create(ns, siblingDb.getFullyQualifiedName()); + Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sib"); + + prefixDelete("/v1/databases/prefix/", targetDb.getId()); + + assertEntityGone(() -> SdkClients.adminClient().databases().get(targetDb.getId().toString()), "target database"); + assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString()), "target schema"); + assertEntityGone(() -> SdkClients.adminClient().tables().get(targetTable.getId().toString()), "target table"); + + assertNotNull(SdkClients.adminClient().databases().get(siblingDb.getId().toString()), "sibling database should survive"); + assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); + assertNotNull(SdkClients.adminClient().tables().get(siblingTable.getId().toString()), "sibling table should survive"); + assertNotNull(SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service should survive"); + } + + // ── Schema-level ───────────────────────────────────────────────────────────── + + @Test + void prefixDelete_schema_removesSchemaAndTablesLeavingSiblingSchemaIntact(TestNamespace ns) throws Exception { + DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); + Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + + DatabaseSchema targetSchema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + List targetTableIds = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Table table = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt" + i); + targetTableIds.add(table.getId()); + } + + DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sib"); + + prefixDelete("/v1/databaseSchemas/prefix/", targetSchema.getId()); + + assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString()), "target schema"); + for (UUID tableId : targetTableIds) { + assertEntityGone(() -> SdkClients.adminClient().tables().get(tableId.toString()), "target table"); + } + + assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); + assertNotNull(SdkClients.adminClient().tables().get(siblingTable.getId().toString()), "sibling table should survive"); + assertNotNull(SdkClients.adminClient().databases().get(database.getId().toString()), "database should survive"); + assertNotNull(SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service should survive"); } - private void prefixDeleteService(UUID serviceId) throws Exception { - String url = SdkClients.getServerUrl() + "/v1/services/databaseServices/prefix/" + serviceId; + // ── Helpers ─────────────────────────────────────────────────────────────────── + + private void prefixDelete(String path, UUID id) throws Exception { + String url = SdkClients.getServerUrl() + path + id; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", "Bearer " + SdkClients.getAdminToken()) @@ -122,7 +177,8 @@ private void prefixDeleteService(UUID serviceId) throws Exception { HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(202, response.statusCode(), - "Expected 202 Accepted from prefix delete, got " + response.statusCode() + ": " + response.body()); + "Expected 202 Accepted from prefix delete " + path + id + + ", got " + response.statusCode() + ": " + response.body()); } private void assertEntityGone(ThrowingRunnable action, String entityType) { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java index 200170085ec4..41bd082cf4b8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java @@ -517,6 +517,27 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDatabasePrefixHard", + summary = "Hard-delete a database and all descendants by FQN prefix", + description = + "Bulk hard-delete a database and all its schemas and tables using FQN prefix matching. " + + "Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Database for instance {id} is not found") + }) + public Response deletePrefixHard( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the database", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/async/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java index 7be0e5fa9441..659bb467693f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java @@ -749,6 +749,29 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDBSchemaPrefixHard", + summary = "Hard-delete a database schema and all descendants by FQN prefix", + description = + "Bulk hard-delete a database schema and all its tables using FQN prefix matching. " + + "Significantly faster than recursive delete for large schemas.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse( + responseCode = "404", + description = "Database schema for instance {id} is not found") + }) + public Response deletePrefixHard( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Database schema Id", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/async/{id}") @Operation( From 9cd10afd2a44f722bc38056a45d33f0ff9997f48 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 16:00:23 +0530 Subject: [PATCH 08/29] refactor: move prefix hard delete endpoint to EntityResource base class Previously DELETE /prefix/{id} was only wired in DatabaseServiceResource (and manually added to Database/DatabaseSchema). Adding it to each of the 60+ entity resource subclasses individually would be maintenance-heavy and error-prone. JAX-RS inherits endpoint annotations from parent classes when the subclass method has no annotations of its own, so adding @DELETE @Path("/prefix/{id}") once to EntityResource.deletePrefixHardById() exposes the endpoint on every entity resource automatically: DELETE /v1/databases/prefix/{id} DELETE /v1/databaseSchemas/prefix/{id} DELETE /v1/tables/prefix/{id} DELETE /v1/services/databaseServices/prefix/{id} DELETE /v1/glossaries/prefix/{id} ... (every EntityResource subclass) Removed the now-redundant overrides from DatabaseServiceResource, DatabaseResource, and DatabaseSchemaResource. Co-Authored-By: Claude Sonnet 4.6 --- .../service/resources/EntityResource.java | 22 ++++++++++++++++- .../resources/databases/DatabaseResource.java | 21 ---------------- .../databases/DatabaseSchemaResource.java | 23 ------------------ .../database/DatabaseServiceResource.java | 24 ------------------- 4 files changed, 21 insertions(+), 69 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java index a8f3c21945d9..8244cf290f47 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java @@ -29,9 +29,11 @@ import jakarta.json.JsonPatch; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -705,7 +707,25 @@ public Response deleteByIdAsync( return response; } - public Response deletePrefixHardById(UriInfo uriInfo, SecurityContext securityContext, UUID id) { + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePrefixHard", + summary = "Hard-delete an entity and all descendants by FQN prefix", + description = + "Bulk hard-delete this entity and all descendants whose FQN starts with this entity's FQN. " + + "Significantly faster than recursive delete for large hierarchies and eliminates " + + "race conditions with concurrent ingestion.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the entity", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { String jobId = UUID.randomUUID().toString(); OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE); authorizer.authorize( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java index 41bd082cf4b8..200170085ec4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java @@ -517,27 +517,6 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE - @Path("/prefix/{id}") - @Operation( - operationId = "deleteDatabasePrefixHard", - summary = "Hard-delete a database and all descendants by FQN prefix", - description = - "Bulk hard-delete a database and all its schemas and tables using FQN prefix matching. " - + "Significantly faster than recursive delete for large hierarchies.", - responses = { - @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), - @ApiResponse(responseCode = "404", description = "Database for instance {id} is not found") - }) - public Response deletePrefixHard( - @Context UriInfo uriInfo, - @Context SecurityContext securityContext, - @Parameter(description = "Id of the database", schema = @Schema(type = "UUID")) - @PathParam("id") - UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); - } - @DELETE @Path("/async/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java index 659bb467693f..7be0e5fa9441 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java @@ -749,29 +749,6 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE - @Path("/prefix/{id}") - @Operation( - operationId = "deleteDBSchemaPrefixHard", - summary = "Hard-delete a database schema and all descendants by FQN prefix", - description = - "Bulk hard-delete a database schema and all its tables using FQN prefix matching. " - + "Significantly faster than recursive delete for large schemas.", - responses = { - @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), - @ApiResponse( - responseCode = "404", - description = "Database schema for instance {id} is not found") - }) - public Response deletePrefixHard( - @Context UriInfo uriInfo, - @Context SecurityContext securityContext, - @Parameter(description = "Database schema Id", schema = @Schema(type = "UUID")) - @PathParam("id") - UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); - } - @DELETE @Path("/async/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java index 764a7d13ef64..394a145c493f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java @@ -732,30 +732,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE - @Path("/prefix/{id}") - @Operation( - operationId = "deleteDatabaseServicePrefixHard", - summary = "Hard-delete a database service and all descendants by FQN prefix", - description = - "Bulk hard-delete a database service and all its databases, schemas, and tables using " - + "FQN prefix matching. This is significantly faster than recursive delete for large " - + "hierarchies and eliminates race conditions with concurrent ingestion.", - responses = { - @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), - @ApiResponse( - responseCode = "404", - description = "DatabaseService for instance {id} is not found") - }) - public Response deletePrefixHard( - @Context UriInfo uriInfo, - @Context SecurityContext securityContext, - @Parameter(description = "Id of the database service", schema = @Schema(type = "UUID")) - @PathParam("id") - UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); - } - @DELETE @Path("/name/{name}") @Operation( From 8343288df9c5a3b67806a3f846213cf928f2fe87 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 16:17:35 +0530 Subject: [PATCH 09/29] fix: await async deletion before asserting in PrefixDeletionIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prefix delete endpoint returns 202 immediately and processes the deletion in a background thread. Asserting right after the response would always race against the job and produce false failures. Replace assertEntityGone() (which asserted synchronously) with awaitGone() backed by Awaitility — polls every 1s for up to 30s until the GET throws, matching the async job lifecycle. Also separate the mixed descendant ID list into typed lists (dbIds, schemaIds, tableIds) so each is fetched via the correct SDK client. Co-Authored-By: Claude Sonnet 4.6 --- .../it/tests/PrefixDeletionIT.java | 74 +++++++++++++------ 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java index 8c72c2fceb49..347d828222b8 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java @@ -15,15 +15,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -49,10 +50,16 @@ *
  • {@code DELETE /v1/databaseSchemas/prefix/{id}} — deletes schema + tables, leaving sibling * schemas intact * + * + *

    The endpoint is async (returns 202 with a jobId). All assertions use Awaitility to poll until + * the background deletion actually completes. */ @ExtendWith(TestNamespaceExtension.class) public class PrefixDeletionIT { + private static final Duration DELETE_TIMEOUT = Duration.ofSeconds(30); + private static final Duration POLL_INTERVAL = Duration.ofSeconds(1); + @BeforeAll static void setup() { SdkClients.adminClient(); @@ -74,11 +81,11 @@ void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) thro prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); - assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); - assertEntityGone(() -> SdkClients.adminClient().databases().get(database.getId().toString()), "database"); - assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(schema.getId().toString()), "schema"); + awaitGone("service", () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); + awaitGone("database", () -> SdkClients.adminClient().databases().get(database.getId().toString())); + awaitGone("schema", () -> SdkClients.adminClient().databaseSchemas().get(schema.getId().toString())); for (UUID tableId : tableIds) { - assertEntityGone(() -> SdkClients.adminClient().tables().get(tableId.toString()), "table"); + awaitGone("table", () -> SdkClients.adminClient().tables().get(tableId.toString())); } } @@ -86,26 +93,34 @@ void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) thro void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - List allDescendantIds = new ArrayList<>(); + List dbIds = new ArrayList<>(); + List schemaIds = new ArrayList<>(); + List tableIds = new ArrayList<>(); + for (int d = 0; d < 2; d++) { Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); - allDescendantIds.add(database.getId()); + dbIds.add(database.getId()); for (int s = 0; s < 2; s++) { DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); - allDescendantIds.add(schema.getId()); + schemaIds.add(schema.getId()); for (int t = 0; t < 3; t++) { Table table = TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + d + s + t); - allDescendantIds.add(table.getId()); + tableIds.add(table.getId()); } } } prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); - assertEntityGone(() -> SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service"); - for (UUID id : allDescendantIds) { - final UUID finalId = id; - assertThrows(Exception.class, () -> SdkClients.adminClient().tables().get(finalId.toString())); + awaitGone("service", () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); + for (UUID id : dbIds) { + awaitGone("database", () -> SdkClients.adminClient().databases().get(id.toString())); + } + for (UUID id : schemaIds) { + awaitGone("schema", () -> SdkClients.adminClient().databaseSchemas().get(id.toString())); + } + for (UUID id : tableIds) { + awaitGone("table", () -> SdkClients.adminClient().tables().get(id.toString())); } } @@ -125,9 +140,9 @@ void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIn prefixDelete("/v1/databases/prefix/", targetDb.getId()); - assertEntityGone(() -> SdkClients.adminClient().databases().get(targetDb.getId().toString()), "target database"); - assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString()), "target schema"); - assertEntityGone(() -> SdkClients.adminClient().tables().get(targetTable.getId().toString()), "target table"); + awaitGone("target database", () -> SdkClients.adminClient().databases().get(targetDb.getId().toString())); + awaitGone("target schema", () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); + awaitGone("target table", () -> SdkClients.adminClient().tables().get(targetTable.getId().toString())); assertNotNull(SdkClients.adminClient().databases().get(siblingDb.getId().toString()), "sibling database should survive"); assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); @@ -154,9 +169,9 @@ void prefixDelete_schema_removesSchemaAndTablesLeavingSiblingSchemaIntact(TestNa prefixDelete("/v1/databaseSchemas/prefix/", targetSchema.getId()); - assertEntityGone(() -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString()), "target schema"); + awaitGone("target schema", () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); for (UUID tableId : targetTableIds) { - assertEntityGone(() -> SdkClients.adminClient().tables().get(tableId.toString()), "target table"); + awaitGone("target table", () -> SdkClients.adminClient().tables().get(tableId.toString())); } assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); @@ -181,13 +196,26 @@ private void prefixDelete(String path, UUID id) throws Exception { + ", got " + response.statusCode() + ": " + response.body()); } - private void assertEntityGone(ThrowingRunnable action, String entityType) { - assertThrows(Exception.class, action::run, - entityType + " should be gone after prefix deletion but was still retrievable"); + /** + * Polls until the given fetch throws (entity gone) or the timeout elapses. + * The prefix delete API is async — the 202 only means the job was queued. + */ + private void awaitGone(String entityType, ThrowingSupplier fetch) { + Awaitility.await("Wait for " + entityType + " to be deleted") + .atMost(DELETE_TIMEOUT) + .pollInterval(POLL_INTERVAL) + .until(() -> { + try { + fetch.get(); + return false; + } catch (Exception e) { + return true; + } + }); } @FunctionalInterface - private interface ThrowingRunnable { - void run() throws Exception; + private interface ThrowingSupplier { + T get() throws Exception; } } From 29cb761d5bea1eb2862baf43ce3efebeb12c736e Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 16:27:14 +0530 Subject: [PATCH 10/29] fix: initialize LockManagerInitializer on startup so PrefixDeletionService is not null LockManagerInitializer.initialize() was never called, leaving PrefixDeletionService.getInstance() returning null. Any call to DELETE /prefix/{id} would immediately NullPointerException in EntityResource.deletePrefixHardById(). Call LockManagerInitializer.initialize() in OpenMetadataApplication.run() after Entity.initializeRepositories() so that both CollectionDAO and all entity repositories are available when the lock manager and PrefixDeletionService are wired up. Co-Authored-By: Claude Sonnet 4.6 --- .../java/org/openmetadata/service/OpenMetadataApplication.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java index 4b0eab6398da..b76d094d231f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java @@ -102,6 +102,7 @@ import org.openmetadata.service.exception.OMErrorPageHandler; import org.openmetadata.service.fernet.Fernet; import org.openmetadata.service.governance.workflows.WorkflowHandler; +import org.openmetadata.service.initialization.LockManagerInitializer; import org.openmetadata.service.jdbi3.BulkExecutor; import org.openmetadata.service.jdbi3.CollectionDAO; import org.openmetadata.service.jdbi3.EntityRelationshipRepository; @@ -275,6 +276,8 @@ public void run(OpenMetadataApplicationConfig catalogConfig, Environment environ ResourceRegistry.addResource( Entity.AUDIT_LOG, List.of(MetadataOperation.AUDIT_LOGS), Collections.emptySet()); + LockManagerInitializer.initialize(); + // Configure the Fernet instance Fernet.getInstance().setFernetKey(catalogConfig); From 9970d960e7f8c32e9af92e69994ac9be5564ff3e Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 16:40:06 +0530 Subject: [PATCH 11/29] fix: use unique names in PrefixDeletionIT/BenchmarkIT to avoid 409 conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old code called DatabaseTestFactory.create(ns, ...) and DatabaseSchemaTestFactory.create(ns, ...) inside loops — both always produced ns.prefix("db") / ns.prefix("schema"), so the second iteration hit a 409 ConflictException: Entity already exists. Fix: - Add DatabaseTestFactory.createWithName(ns, serviceFqn, baseName) and DatabaseSchemaTestFactory.createWithName(ns, databaseFqn, baseName) so callers can provide a distinct base name that gets namespaced. - Rewrite every loop in PrefixDeletionIT and PrefixDeletionBenchmarkIT to pass a loop-index-qualified base name ("db0", "db1", "sc00", ...). Co-Authored-By: Claude Sonnet 4.6 --- .../factories/DatabaseSchemaTestFactory.java | 7 +++++ .../it/factories/DatabaseTestFactory.java | 11 ++++++++ .../it/tests/PrefixDeletionBenchmarkIT.java | 4 +-- .../it/tests/PrefixDeletionIT.java | 28 +++++++++---------- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java index c3910fb9d9fe..a8a9bf96e0b3 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java @@ -49,6 +49,13 @@ public static DatabaseSchema createSimple(TestNamespace ns) { return createSimple(ns, service); } + /** + * Create a schema with a namespaced base name using fluent API. + */ + public static DatabaseSchema createWithName(TestNamespace ns, String databaseFqn, String baseName) { + return DatabaseSchemas.create().name(ns.prefix(baseName)).in(databaseFqn).execute(); + } + /** * Create a schema with a custom name using fluent API. * Useful for tests that need short names to avoid FQN length limits. diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseTestFactory.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseTestFactory.java index fb7e4427f789..ac7c8fdc9aab 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseTestFactory.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseTestFactory.java @@ -29,4 +29,15 @@ public static Database create(TestNamespace ns, String serviceFqn) { public static Database createWithName(String serviceFqn, String name) { return Databases.create().name(name).in(serviceFqn).execute(); } + + /** + * Create database with a namespaced base name using fluent API. + */ + public static Database createWithName(TestNamespace ns, String serviceFqn, String baseName) { + return Databases.create() + .name(ns.prefix(baseName)) + .in(serviceFqn) + .withDescription("Test database created by integration test") + .execute(); + } } diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java index 14acd008de37..d712e7ad845d 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java @@ -94,9 +94,9 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) { LOG.info("[{}] Creating hierarchy under service: {}", tag, service.getName()); for (int d = 0; d < DATABASES_PER_SERVICE; d++) { - Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), tag + "db" + d); for (int s = 0; s < SCHEMAS_PER_DATABASE; s++) { - DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), tag + "sc" + d + s); for (int t = 0; t < TABLES_PER_SCHEMA; t++) { TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), tag + "t" + d + s + t); } diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java index 347d828222b8..5b0e6a7ffc52 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java @@ -70,8 +70,8 @@ static void setup() { @Test void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); - DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); + DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sc"); List tableIds = new ArrayList<>(); for (int i = 0; i < 5; i++) { @@ -98,10 +98,10 @@ void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) thro List tableIds = new ArrayList<>(); for (int d = 0; d < 2; d++) { - Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db" + d); dbIds.add(database.getId()); for (int s = 0; s < 2; s++) { - DatabaseSchema schema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sc" + d + s); schemaIds.add(schema.getId()); for (int t = 0; t < 3; t++) { Table table = TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + d + s + t); @@ -130,13 +130,13 @@ void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) thro void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIntact(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database targetDb = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); - DatabaseSchema targetSchema = DatabaseSchemaTestFactory.create(ns, targetDb.getFullyQualifiedName()); - Table targetTable = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt"); + Database targetDb = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "tgtdb"); + DatabaseSchema targetSchema = DatabaseSchemaTestFactory.createWithName(ns, targetDb.getFullyQualifiedName(), "tgtsc"); + Table targetTable = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgttbl"); - Database siblingDb = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); - DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.create(ns, siblingDb.getFullyQualifiedName()); - Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sib"); + Database siblingDb = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "sibdb"); + DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.createWithName(ns, siblingDb.getFullyQualifiedName(), "sibsc"); + Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); prefixDelete("/v1/databases/prefix/", targetDb.getId()); @@ -155,17 +155,17 @@ void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIn @Test void prefixDelete_schema_removesSchemaAndTablesLeavingSiblingSchemaIntact(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database database = DatabaseTestFactory.create(ns, service.getFullyQualifiedName()); + Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); - DatabaseSchema targetSchema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); + DatabaseSchema targetSchema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "tgtsc"); List targetTableIds = new ArrayList<>(); for (int i = 0; i < 3; i++) { Table table = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt" + i); targetTableIds.add(table.getId()); } - DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.create(ns, database.getFullyQualifiedName()); - Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sib"); + DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sibsc"); + Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); prefixDelete("/v1/databaseSchemas/prefix/", targetSchema.getId()); From 670e3d2e9189e291822a7f766cdb128d7a41d0ff Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 16:46:31 +0530 Subject: [PATCH 12/29] feat: scale benchmark to 10k entities and measure actual async deletion time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Default topology changed to 5 × 5 × 400 = 10,000 tables per service (~10,031 total entities), all configurable via -Dtest.databases/schemas/tables - timeNewDelete now polls with Awaitility until the service is actually gone so the reported elapsed time covers real deletion, not just the 202 handoff - Log messages improved to show total entity count and seeding time estimate - Removed unused HashMap/Map imports, replaced with Duration/UUID/Awaitility Co-Authored-By: Claude Sonnet 4.6 --- .../it/tests/PrefixDeletionBenchmarkIT.java | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java index d712e7ad845d..ea70a0bcbb94 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java @@ -17,8 +17,9 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.HashMap; -import java.util.Map; +import java.time.Duration; +import java.util.UUID; +import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; @@ -40,17 +41,25 @@ /** * Benchmark comparing old recursive hard delete vs new FQN prefix hard delete. * - *

    Run manually against a local stack with a pre-populated database: + *

    Default topology: 5 databases × 5 schemas × 400 tables = 10,000 tables per service + * (~10,031 total entities including service, databases, schemas). + * + *

    Run manually against a local stack: * *

      *   mvn verify -pl openmetadata-integration-tests \
      *     -Dgroups=benchmark \
      *     -Dit.test=PrefixDeletionBenchmarkIT \
    - *     -Dtest.scenario=100   # tables per schema (default: 50)
    + *     -Dtest.databases=5    # databases per service (default: 5)
    + *     -Dtest.schemas=5      # schemas per database (default: 5)
    + *     -Dtest.tables=400     # tables per schema    (default: 400)
      * 
    * - *

    Results are printed to the log. The prefix approach should be 3-10x faster for large - * hierarchies because it issues one bulk DELETE per table vs one round-trip per entity. + *

    NOTE: Setup creates entities sequentially via REST — at ~50ms/call expect ~15 min of + * data seeding per hierarchy before deletion timing begins. + * + *

    Both deletions are timed end-to-end: the old delete is synchronous; the new prefix + * delete is async (202), so we poll until the service is gone before recording elapsed time. */ @Tag("benchmark") @Disabled("Manual benchmark — run explicitly against a local mysql/postgres stack") @@ -59,10 +68,12 @@ class PrefixDeletionBenchmarkIT { private static final Logger LOG = LoggerFactory.getLogger(PrefixDeletionBenchmarkIT.class); - private static final int DATABASES_PER_SERVICE = 2; - private static final int SCHEMAS_PER_DATABASE = 2; - private static final int TABLES_PER_SCHEMA = - Integer.getInteger("test.scenario", 50); + private static final int DATABASES_PER_SERVICE = Integer.getInteger("test.databases", 5); + private static final int SCHEMAS_PER_DATABASE = Integer.getInteger("test.schemas", 5); + private static final int TABLES_PER_SCHEMA = Integer.getInteger("test.tables", 400); + + private static final Duration DELETE_POLL_TIMEOUT = Duration.ofMinutes(10); + private static final Duration DELETE_POLL_INTERVAL = Duration.ofSeconds(2); @BeforeAll static void setup() { @@ -72,9 +83,12 @@ static void setup() { @Test void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throws Exception { int totalTables = DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; + int totalEntities = 1 + DATABASES_PER_SERVICE + + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE + + totalTables; LOG.info( - "Benchmark: {} databases × {} schemas × {} tables = {} total tables per service", - DATABASES_PER_SERVICE, SCHEMAS_PER_DATABASE, TABLES_PER_SCHEMA, totalTables); + "Benchmark topology: {} databases × {} schemas × {} tables = {} tables, {} total entities per service", + DATABASES_PER_SERVICE, SCHEMAS_PER_DATABASE, TABLES_PER_SCHEMA, totalTables, totalEntities); DatabaseService oldService = buildHierarchy(ns, "old"); long oldMs = timeOldDelete(oldService); @@ -83,15 +97,15 @@ void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throw long newMs = timeNewDelete(newService); double speedup = (double) oldMs / Math.max(newMs, 1); - LOG.info("=== Deletion Benchmark Results ({} tables per service) ===", totalTables); + LOG.info("=== Deletion Benchmark Results ({} entities per service) ===", totalEntities); LOG.info(" Old recursive hard delete : {} ms", oldMs); LOG.info(" New FQN prefix hard delete: {} ms", newMs); - LOG.info(" Speedup : {:.2f}x", speedup); + LOG.info(" Speedup : {}x", String.format("%.2f", speedup)); } private DatabaseService buildHierarchy(TestNamespace ns, String tag) { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - LOG.info("[{}] Creating hierarchy under service: {}", tag, service.getName()); + LOG.info("[{}] Seeding hierarchy under service {} ...", tag, service.getName()); for (int d = 0; d < DATABASES_PER_SERVICE; d++) { Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), tag + "db" + d); @@ -108,7 +122,7 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) { } private long timeOldDelete(DatabaseService service) throws Exception { - LOG.info("Starting OLD recursive hard delete for service {}", service.getName()); + LOG.info("Timing OLD recursive hard delete for service {} ...", service.getName()); long start = System.currentTimeMillis(); String url = SdkClients.getServerUrl() @@ -122,13 +136,28 @@ private long timeOldDelete(DatabaseService service) throws Exception { } private long timeNewDelete(DatabaseService service) throws Exception { - LOG.info("Starting NEW FQN prefix hard delete for service {}", service.getName()); + LOG.info("Timing NEW FQN prefix hard delete for service {} ...", service.getName()); long start = System.currentTimeMillis(); String url = SdkClients.getServerUrl() + "/v1/services/databaseServices/prefix/" + service.getId(); sendDelete(url); + // Prefix delete is async — poll until the service is actually gone so we measure + // real deletion time, not just the time to hand off the job to the executor. + UUID serviceId = service.getId(); + Awaitility.await("Wait for prefix deletion of " + service.getName() + " to complete") + .atMost(DELETE_POLL_TIMEOUT) + .pollInterval(DELETE_POLL_INTERVAL) + .until(() -> { + try { + SdkClients.adminClient().databaseServices().get(serviceId.toString()); + return false; + } catch (Exception e) { + return true; + } + }); + long elapsed = System.currentTimeMillis() - start; LOG.info("NEW FQN prefix hard delete completed in {} ms", elapsed); return elapsed; From 71818409adb14f87ec08c6ed3acd226de9051c65 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 17:16:32 +0530 Subject: [PATCH 13/29] perf: parallelize entity seeding in PrefixDeletionBenchmarkIT Replace sequential triple-nested loop with a 3-phase parallel approach: 1. All databases created concurrently 2. All schemas created concurrently (after databases resolve) 3. All tables created concurrently (after schemas resolve) Default 32 threads (tunable via -Dtest.seedThreads). At ~50ms/REST call and 32 threads, 10k tables seed in ~20s instead of ~8 min sequentially. Also logs per-entity average seed time for diagnostics. Co-Authored-By: Claude Sonnet 4.6 --- .../it/tests/PrefixDeletionBenchmarkIT.java | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java index ea70a0bcbb94..8c2351b3c388 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java @@ -18,7 +18,13 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; @@ -50,13 +56,14 @@ * mvn verify -pl openmetadata-integration-tests \ * -Dgroups=benchmark \ * -Dit.test=PrefixDeletionBenchmarkIT \ - * -Dtest.databases=5 # databases per service (default: 5) - * -Dtest.schemas=5 # schemas per database (default: 5) - * -Dtest.tables=400 # tables per schema (default: 400) + * -Dtest.databases=5 # databases per service (default: 5) + * -Dtest.schemas=5 # schemas per database (default: 5) + * -Dtest.tables=400 # tables per schema (default: 400) + * -Dtest.seedThreads=32 # parallel seed threads (default: 32) * * - *

    NOTE: Setup creates entities sequentially via REST — at ~50ms/call expect ~15 min of - * data seeding per hierarchy before deletion timing begins. + *

    NOTE: Setup creates entities in parallel (default 32 threads, tunable via + * -Dtest.seedThreads). At ~50ms/call and 32 threads, 10k tables seed in ~20 s. * *

    Both deletions are timed end-to-end: the old delete is synchronous; the new prefix * delete is async (202), so we poll until the service is gone before recording elapsed time. @@ -71,6 +78,7 @@ class PrefixDeletionBenchmarkIT { private static final int DATABASES_PER_SERVICE = Integer.getInteger("test.databases", 5); private static final int SCHEMAS_PER_DATABASE = Integer.getInteger("test.schemas", 5); private static final int TABLES_PER_SCHEMA = Integer.getInteger("test.tables", 400); + private static final int SEED_THREADS = Integer.getInteger("test.seedThreads", 32); private static final Duration DELETE_POLL_TIMEOUT = Duration.ofMinutes(10); private static final Duration DELETE_POLL_INTERVAL = Duration.ofSeconds(2); @@ -103,21 +111,66 @@ void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throw LOG.info(" Speedup : {}x", String.format("%.2f", speedup)); } - private DatabaseService buildHierarchy(TestNamespace ns, String tag) { + private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - LOG.info("[{}] Seeding hierarchy under service {} ...", tag, service.getName()); + int totalEntities = 1 + DATABASES_PER_SERVICE + + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE + + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; + LOG.info("[{}] Seeding {} entities under service {} using {} threads ...", + tag, totalEntities, service.getName(), SEED_THREADS); + long seedStart = System.currentTimeMillis(); + + ExecutorService pool = Executors.newFixedThreadPool(SEED_THREADS); + try { + List> dbFutures = new ArrayList<>(); + for (int d = 0; d < DATABASES_PER_SERVICE; d++) { + final int dIdx = d; + dbFutures.add(pool.submit(() -> + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), tag + "db" + dIdx))); + } + List databases = new ArrayList<>(); + for (Future f : dbFutures) { + databases.add(f.get()); + } - for (int d = 0; d < DATABASES_PER_SERVICE; d++) { - Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), tag + "db" + d); - for (int s = 0; s < SCHEMAS_PER_DATABASE; s++) { - DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), tag + "sc" + d + s); + List> schemaFutures = new ArrayList<>(); + for (int d = 0; d < databases.size(); d++) { + final Database database = databases.get(d); + final int dIdx = d; + for (int s = 0; s < SCHEMAS_PER_DATABASE; s++) { + final int sIdx = s; + schemaFutures.add(pool.submit(() -> + DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), tag + "sc" + dIdx + "x" + sIdx))); + } + } + List schemas = new ArrayList<>(); + for (Future f : schemaFutures) { + schemas.add(f.get()); + } + + List> tableFutures = new ArrayList<>(); + for (int s = 0; s < schemas.size(); s++) { + final DatabaseSchema schema = schemas.get(s); + final int sIdx = s; for (int t = 0; t < TABLES_PER_SCHEMA; t++) { - TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), tag + "t" + d + s + t); + final int tIdx = t; + tableFutures.add(pool.submit(() -> { + TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), tag + "tbl" + sIdx + "x" + tIdx); + return null; + })); } } + for (Future f : tableFutures) { + f.get(); + } + } finally { + pool.shutdown(); + pool.awaitTermination(30, TimeUnit.MINUTES); } - LOG.info("[{}] Hierarchy created.", tag); + long seedMs = System.currentTimeMillis() - seedStart; + LOG.info("[{}] Hierarchy seeded in {} ms ({} ms/entity avg)", + tag, seedMs, seedMs / Math.max(totalEntities, 1)); return service; } From 64735da388812b713224317ad9396ac42a48fc05 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 18:32:45 +0530 Subject: [PATCH 14/29] fix: use VARCHAR(36) for UUID columns in entity_deletion_lock on PostgreSQL The rest of the codebase uses VARCHAR(36) for UUID columns in PostgreSQL. BindUUID binds UUID values as strings (via UUID.toString()), which works with VARCHAR(36) but fails with native UUID columns because PostgreSQL rejects the implicit text = uuid comparison: Hint: No operator matches the given name and argument types. Changing id and entityId to VARCHAR(36) makes the schema consistent with every other entity table and removes the type cast error. Co-Authored-By: Claude Sonnet 4.6 --- .../sql/migrations/native/1.9.0/postgres/schemaChanges.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql index f28869bf8a4f..fe52da659d86 100644 --- a/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql @@ -152,8 +152,8 @@ WHERE relation IN (0, 8); -- Entity deletion lock table for preventing orphaned entities during cascade deletion CREATE TABLE IF NOT EXISTS entity_deletion_lock ( - id UUID NOT NULL DEFAULT gen_random_uuid(), - entityId UUID NOT NULL, + id VARCHAR(36) NOT NULL, + entityId VARCHAR(36) NOT NULL, entityType VARCHAR(256) NOT NULL, entityFqn VARCHAR(2048) NOT NULL, lockType VARCHAR(50) NOT NULL, -- 'DELETE_IN_PROGRESS', 'DELETE_SCHEDULED' From da2f6728f32106ff949746dbea8e6500ef528bf2 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 18:36:27 +0530 Subject: [PATCH 15/29] Revert "fix: use VARCHAR(36) for UUID columns in entity_deletion_lock on PostgreSQL" This reverts commit 87aa7f3eebb1dcc20e4d8aa53b07d57682dd8d54. --- .../sql/migrations/native/1.9.0/postgres/schemaChanges.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql index fe52da659d86..f28869bf8a4f 100644 --- a/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.9.0/postgres/schemaChanges.sql @@ -152,8 +152,8 @@ WHERE relation IN (0, 8); -- Entity deletion lock table for preventing orphaned entities during cascade deletion CREATE TABLE IF NOT EXISTS entity_deletion_lock ( - id VARCHAR(36) NOT NULL, - entityId VARCHAR(36) NOT NULL, + id UUID NOT NULL DEFAULT gen_random_uuid(), + entityId UUID NOT NULL, entityType VARCHAR(256) NOT NULL, entityFqn VARCHAR(2048) NOT NULL, lockType VARCHAR(50) NOT NULL, -- 'DELETE_IN_PROGRESS', 'DELETE_SCHEDULED' From 170e0ed78fb8c1643ab3256be2ce5532ce9f1824 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 18:36:47 +0530 Subject: [PATCH 16/29] fix: use VARCHAR(36) for UUID columns in entity_deletion_lock on PostgreSQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The entity_deletion_lock table was created in 1.9.0 with native UUID columns for id and entityId. The rest of the codebase uses VARCHAR(36) for all UUID columns so that BindUUID (which binds via UUID.toString()) can compare them without an explicit type cast. Without this, PostgreSQL rejects the WHERE clause in DeletionLockDAO: Hint: No operator matches the given name and argument types. You might need to add explicit type casts. ALTER TABLE migrates existing rows in place — UUID::VARCHAR produces the standard 8-4-4-4-12 string representation. Co-Authored-By: Claude Sonnet 4.6 --- .../migrations/native/2.0.0/postgres/schemaChanges.sql | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql index 326bb4b13ca9..bb19ccc9f6cb 100644 --- a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql @@ -1 +1,8 @@ --- MCP tables are created in 1.13.0 migration. This file is intentionally empty. \ No newline at end of file +-- MCP tables are created in 1.13.0 migration. This file is intentionally empty. + +-- Fix entity_deletion_lock column types: id and entityId were created as native UUID +-- in 1.9.0 but the rest of the codebase uses VARCHAR(36) for UUID columns so that +-- BindUUID (which binds via UUID.toString()) can compare them without an explicit cast. +ALTER TABLE entity_deletion_lock + ALTER COLUMN id TYPE VARCHAR(36) USING id::VARCHAR, + ALTER COLUMN entityId TYPE VARCHAR(36) USING entityId::VARCHAR; \ No newline at end of file From a5625ec25613522b272abd9923e341f247f070d3 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 18:38:45 +0530 Subject: [PATCH 17/29] fix: use VARCHAR(36) for UUID columns in entity_deletion_lock on PostgreSQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The entity_deletion_lock table was created in 1.9.0 with native UUID columns for id and entityId. The rest of the codebase uses VARCHAR(36) for all UUID columns so that BindUUID (which binds via UUID.toString()) can compare them without an explicit type cast. Without this, PostgreSQL rejects the WHERE clause in DeletionLockDAO: Hint: No operator matches the given name and argument types. You might need to add explicit type casts. ALTER TABLE migrates existing rows in place — UUID::VARCHAR produces the standard 8-4-4-4-12 string representation. Co-Authored-By: Claude Sonnet 4.6 --- .../migrations/native/1.13.0/postgres/schemaChanges.sql | 7 +++++++ .../migrations/native/2.0.0/postgres/schemaChanges.sql | 9 +-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql index b6d911588ec2..5aa37757b52a 100644 --- a/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/postgres/schemaChanges.sql @@ -160,3 +160,10 @@ ALTER TABLE entity_relationship CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash); CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash); + +-- Fix entity_deletion_lock column types: id and entityId were created as native UUID +-- in 1.9.0 but the rest of the codebase uses VARCHAR(36) for UUID columns so that +-- BindUUID (which binds via UUID.toString()) can compare them without an explicit cast. +ALTER TABLE entity_deletion_lock + ALTER COLUMN id TYPE VARCHAR(36) USING id::VARCHAR, + ALTER COLUMN entityId TYPE VARCHAR(36) USING entityId::VARCHAR; diff --git a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql index bb19ccc9f6cb..326bb4b13ca9 100644 --- a/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/2.0.0/postgres/schemaChanges.sql @@ -1,8 +1 @@ --- MCP tables are created in 1.13.0 migration. This file is intentionally empty. - --- Fix entity_deletion_lock column types: id and entityId were created as native UUID --- in 1.9.0 but the rest of the codebase uses VARCHAR(36) for UUID columns so that --- BindUUID (which binds via UUID.toString()) can compare them without an explicit cast. -ALTER TABLE entity_deletion_lock - ALTER COLUMN id TYPE VARCHAR(36) USING id::VARCHAR, - ALTER COLUMN entityId TYPE VARCHAR(36) USING entityId::VARCHAR; \ No newline at end of file +-- MCP tables are created in 1.13.0 migration. This file is intentionally empty. \ No newline at end of file From f13f96a5bb5d2fff72a931f1fc04c365cd6cd480 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 18:46:00 +0530 Subject: [PATCH 18/29] fix: batch IN-clause deletes to avoid PostgreSQL 65535 parameter limit PostgreSQL limits prepared statements to 65,535 parameters. With 10k+ entities each having multiple extension rows, deleteAllBatch was generating statements with 100k+ parameters and failing with: PSQLException: PreparedStatement can have at most 65,535 parameters. Wrap both EntityExtensionDAO.deleteAllBatch and UsageDAO.deleteBatch with default interface methods that chunk into 50,000-ID slices before delegating to the raw @SqlUpdate methods. 50k is safely below the limit while keeping the number of round trips minimal. Co-Authored-By: Claude Sonnet 4.6 --- .../service/jdbi3/CollectionDAO.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 5cef80ac385b..0f41caaca365 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -1419,7 +1419,14 @@ List getExtensionsWithOffset( void deleteAll(@BindUUID("id") UUID id); @SqlUpdate("DELETE FROM entity_extension WHERE id IN ()") - void deleteAllBatch(@BindList("ids") List ids); + void deleteAllBatchChunk(@BindList("ids") List ids); + + default void deleteAllBatch(List ids) { + int chunkSize = 50_000; + for (int i = 0; i < ids.size(); i += chunkSize) { + deleteAllBatchChunk(ids.subList(i, Math.min(i + chunkSize, ids.size()))); + } + } } class EntityVersionPair { @@ -6211,7 +6218,14 @@ List getUsageById( void delete(@BindUUID("id") UUID id); @SqlUpdate("DELETE FROM entity_usage WHERE id IN ()") - void deleteBatch(@BindList("ids") List ids); + void deleteBatchChunk(@BindList("ids") List ids); + + default void deleteBatch(List ids) { + int chunkSize = 50_000; + for (int i = 0; i < ids.size(); i += chunkSize) { + deleteBatchChunk(ids.subList(i, Math.min(i + chunkSize, ids.size()))); + } + } /** * TODO: Not sure I get what the next comment means, but tests now use mysql 8 so maybe tests can be improved here From ca4183b21db87f46b9964d4f593225ee41d11962 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 22:32:32 +0530 Subject: [PATCH 19/29] refactor: move prefix hard delete endpoint to DatabaseServiceResource The codebase pattern is: EntityResource provides the implementation with no JAX-RS annotations; each subclass explicitly declares the endpoint with @DELETE/@Path/@Operation so it appears in the Swagger UI and is routed correctly by JAX-RS. - Strip @DELETE/@Path/@Operation from EntityResource.deletePrefixHardById so it becomes a plain implementation method (same as delete/deleteByIdAsync) - Add explicit @DELETE /prefix/{id} override in DatabaseServiceResource that calls the base implementation Co-Authored-By: Claude Sonnet 4.6 --- .../service/resources/EntityResource.java | 21 +++------------- .../database/DatabaseServiceResource.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java index 8244cf290f47..ea2eda5057b9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java @@ -707,25 +707,10 @@ public Response deleteByIdAsync( return response; } - @DELETE - @Path("/prefix/{id}") - @Operation( - operationId = "deletePrefixHard", - summary = "Hard-delete an entity and all descendants by FQN prefix", - description = - "Bulk hard-delete this entity and all descendants whose FQN starts with this entity's FQN. " - + "Significantly faster than recursive delete for large hierarchies and eliminates " - + "race conditions with concurrent ingestion.", - responses = { - @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), - @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") - }) public Response deletePrefixHardById( - @Context UriInfo uriInfo, - @Context SecurityContext securityContext, - @Parameter(description = "Id of the entity", schema = @Schema(type = "UUID")) - @PathParam("id") - UUID id) { + UriInfo uriInfo, + SecurityContext securityContext, + UUID id) { String jobId = UUID.randomUUID().toString(); OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE); authorizer.authorize( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java index 394a145c493f..76c78305f66c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java @@ -732,6 +732,30 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDatabaseServicePrefixHard", + summary = "Hard-delete a database service and all descendants by FQN prefix", + description = + "Bulk hard-delete this database service and all descendants (databases, schemas, tables) " + + "whose FQN starts with this service's FQN. Significantly faster than recursive " + + "delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse( + responseCode = "404", + description = "DatabaseService for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the database service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( From 9321871c678f94281dcd79d5dc6921358741475d Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 22:38:13 +0530 Subject: [PATCH 20/29] feat: expose DELETE /prefix/{id} endpoint on all EntityResource subclasses Add the hard-delete-by-FQN-prefix endpoint to all 69 EntityResource subclasses following the established pattern: each resource class explicitly declares @DELETE @Path("/prefix/{id}") with entity-specific @Operation metadata and delegates to EntityResource.deletePrefixHardById. Resources covered: - 13 service resources (database, storage, messaging, pipeline, ml, metadata, search, security, mcp, llm, drive, dashboard, api) - Database hierarchy (database, schema, table, stored procedure) - Data entities (topic, container, search index, pipeline, ml model, dashboard, chart, dashboard data model, api collection, api endpoint) - Team/user/role (team, user, role, persona, policy, bot) - Classification/taxonomy (classification, tag, glossary, glossary term, domain, data product) - Tests/quality (test suite, test case, test definition, test connection definition, data contract, kpi, metric, query) - Apps/workflows (app, marketplace, workflow, workflow definition, ingestion pipeline, event subscription, notification template) - AI (ai application, ai governance policy, llm model, mcp server, prompt template) - Drives (directory, file, spreadsheet, worksheet) - Misc (type, report, doc store, data insight chart, system chart, web analytic event, learning resource) Also adds missing imports (DELETE, PathParam, Parameter, ApiResponse, Schema) to the 5 files that lacked them. Co-Authored-By: Claude Sonnet 4.6 --- .../resources/ai/AIApplicationResource.java | 22 +++++++++++++++++ .../ai/AIGovernancePolicyResource.java | 22 +++++++++++++++++ .../resources/ai/LLMModelResource.java | 22 +++++++++++++++++ .../resources/ai/McpServerResource.java | 22 +++++++++++++++++ .../resources/ai/PromptTemplateResource.java | 22 +++++++++++++++++ .../analytics/WebAnalyticEventResource.java | 22 +++++++++++++++++ .../resources/apis/APICollectionResource.java | 22 +++++++++++++++++ .../resources/apis/APIEndpointResource.java | 22 +++++++++++++++++ .../apps/AppMarketPlaceResource.java | 22 +++++++++++++++++ .../service/resources/apps/AppResource.java | 22 +++++++++++++++++ .../automations/WorkflowResource.java | 22 +++++++++++++++++ .../service/resources/bots/BotResource.java | 22 +++++++++++++++++ .../resources/charts/ChartResource.java | 22 +++++++++++++++++ .../dashboards/DashboardResource.java | 22 +++++++++++++++++ .../resources/data/DataContractResource.java | 22 +++++++++++++++++ .../resources/databases/DatabaseResource.java | 22 +++++++++++++++++ .../databases/DatabaseSchemaResource.java | 22 +++++++++++++++++ .../databases/StoredProcedureResource.java | 24 +++++++++++++++++++ .../resources/databases/TableResource.java | 22 +++++++++++++++++ .../datainsight/DataInsightChartResource.java | 22 +++++++++++++++++ .../DataInsightSystemChartResource.java | 22 +++++++++++++++++ .../DashboardDataModelResource.java | 22 +++++++++++++++++ .../resources/docstore/DocStoreResource.java | 22 +++++++++++++++++ .../domains/DataProductResource.java | 22 +++++++++++++++++ .../resources/domains/DomainResource.java | 22 +++++++++++++++++ .../resources/dqtests/TestCaseResource.java | 22 +++++++++++++++++ .../dqtests/TestDefinitionResource.java | 22 +++++++++++++++++ .../resources/dqtests/TestSuiteResource.java | 22 +++++++++++++++++ .../resources/drives/DirectoryResource.java | 22 +++++++++++++++++ .../resources/drives/FileResource.java | 22 +++++++++++++++++ .../resources/drives/SpreadsheetResource.java | 22 +++++++++++++++++ .../resources/drives/WorksheetResource.java | 22 +++++++++++++++++ .../events/NotificationTemplateResource.java | 22 +++++++++++++++++ .../EventSubscriptionResource.java | 22 +++++++++++++++++ .../resources/glossary/GlossaryResource.java | 22 +++++++++++++++++ .../glossary/GlossaryTermResource.java | 22 +++++++++++++++++ .../WorkflowDefinitionResource.java | 22 +++++++++++++++++ .../service/resources/kpi/KpiResource.java | 22 +++++++++++++++++ .../learning/LearningResourceResource.java | 22 +++++++++++++++++ .../resources/metrics/MetricResource.java | 22 +++++++++++++++++ .../resources/mlmodels/MlModelResource.java | 22 +++++++++++++++++ .../resources/pipelines/PipelineResource.java | 22 +++++++++++++++++ .../resources/policies/PolicyResource.java | 22 +++++++++++++++++ .../resources/query/QueryResource.java | 22 +++++++++++++++++ .../resources/reports/ReportResource.java | 23 ++++++++++++++++++ .../searchindex/SearchIndexResource.java | 22 +++++++++++++++++ .../apiservices/APIServiceResource.java | 22 +++++++++++++++++ .../TestConnectionDefinitionResource.java | 23 ++++++++++++++++++ .../dashboard/DashboardServiceResource.java | 22 +++++++++++++++++ .../services/drive/DriveServiceResource.java | 22 +++++++++++++++++ .../IngestionPipelineResource.java | 22 +++++++++++++++++ .../services/llm/LLMServiceResource.java | 22 +++++++++++++++++ .../services/mcp/McpServiceResource.java | 22 +++++++++++++++++ .../messaging/MessagingServiceResource.java | 22 +++++++++++++++++ .../metadata/MetadataServiceResource.java | 22 +++++++++++++++++ .../mlmodel/MlModelServiceResource.java | 22 +++++++++++++++++ .../pipeline/PipelineServiceResource.java | 22 +++++++++++++++++ .../searchIndexes/SearchServiceResource.java | 22 +++++++++++++++++ .../security/SecurityServiceResource.java | 22 +++++++++++++++++ .../storage/StorageServiceResource.java | 22 +++++++++++++++++ .../resources/storages/ContainerResource.java | 22 +++++++++++++++++ .../tags/ClassificationResource.java | 22 +++++++++++++++++ .../service/resources/tags/TagResource.java | 22 +++++++++++++++++ .../resources/teams/PersonaResource.java | 24 +++++++++++++++++++ .../service/resources/teams/RoleResource.java | 22 +++++++++++++++++ .../service/resources/teams/TeamResource.java | 22 +++++++++++++++++ .../service/resources/teams/UserResource.java | 22 +++++++++++++++++ .../resources/topics/TopicResource.java | 22 +++++++++++++++++ .../service/resources/types/TypeResource.java | 22 +++++++++++++++++ 69 files changed, 1524 insertions(+) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java index 2a8d91b6ac11..fcdc25c5732a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java @@ -510,6 +510,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteAiApplicationPrefixHard", + summary = "Hard-delete a AI application and all descendants by FQN prefix", + description = + "Bulk hard-delete this AI application and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the AI application", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java index a469b705e839..9634159ab461 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java @@ -497,6 +497,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteAiGovernancePolicyPrefixHard", + summary = "Hard-delete a AI governance policy and all descendants by FQN prefix", + description = + "Bulk hard-delete this AI governance policy and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the AI governance policy", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java index 1bfbc7aeb4fe..cce2bfa208aa 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java @@ -477,6 +477,28 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteLlmModelPrefixHard", + summary = "Hard-delete a LLM model and all descendants by FQN prefix", + description = + "Bulk hard-delete this LLM model and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the LLM model", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java index 7a602ee98364..37dceaf5c24a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java @@ -531,6 +531,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMcpServerPrefixHard", + summary = "Hard-delete a MCP server and all descendants by FQN prefix", + description = + "Bulk hard-delete this MCP server and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the MCP server", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java index 416ec1d43bdb..bafff12cd31e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java @@ -494,6 +494,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePromptTemplatePrefixHard", + summary = "Hard-delete a prompt template and all descendants by FQN prefix", + description = + "Bulk hard-delete this prompt template and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the prompt template", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java index d7471083d91c..d60b63ee5f2c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java @@ -352,6 +352,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteWebAnalyticEventPrefixHard", + summary = "Hard-delete a web analytic event and all descendants by FQN prefix", + description = + "Bulk hard-delete this web analytic event and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the web analytic event", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java index 3f575db7f5c2..44b35fdf1c73 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java @@ -545,6 +545,28 @@ public Response updateVote( .toResponse(); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteApiCollectionPrefixHard", + summary = "Hard-delete a API collection and all descendants by FQN prefix", + description = + "Bulk hard-delete this API collection and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the API collection", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java index 4f76cd9e7123..d09c56988dba 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java @@ -591,6 +591,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteApiEndpointPrefixHard", + summary = "Hard-delete a API endpoint and all descendants by FQN prefix", + description = + "Bulk hard-delete this API endpoint and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the API endpoint", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java index 168650c7a4cf..cc314dbadb7b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java @@ -374,6 +374,28 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, app); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteAppMarketPlacePrefixHard", + summary = "Hard-delete a app marketplace definition and all descendants by FQN prefix", + description = + "Bulk hard-delete this app marketplace definition and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the app marketplace definition", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java index 5ab4f8ac5e5e..08d350fdc2dc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java @@ -921,6 +921,28 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, app); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteAppPrefixHard", + summary = "Hard-delete a app and all descendants by FQN prefix", + description = + "Bulk hard-delete this app and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the app", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java index 6e97b951fc10..8c8239431804 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java @@ -529,6 +529,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteWorkflowPrefixHard", + summary = "Hard-delete a workflow and all descendants by FQN prefix", + description = + "Bulk hard-delete this workflow and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the workflow", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java index 6175e4eac8fd..18ef3037adb7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java @@ -435,6 +435,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteBotPrefixHard", + summary = "Hard-delete a bot and all descendants by FQN prefix", + description = + "Bulk hard-delete this bot and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the bot", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java index 96ff75878e9e..da599956deff 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java @@ -530,6 +530,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteChartPrefixHard", + summary = "Hard-delete a chart and all descendants by FQN prefix", + description = + "Bulk hard-delete this chart and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the chart", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java index 3ac05657402a..477c7c1706f3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java @@ -575,6 +575,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDashboardPrefixHard", + summary = "Hard-delete a dashboard and all descendants by FQN prefix", + description = + "Bulk hard-delete this dashboard and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the dashboard", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java index eaffd7591f8c..acacf588dd2d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java @@ -550,6 +550,28 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDataContractPrefixHard", + summary = "Hard-delete a data contract and all descendants by FQN prefix", + description = + "Bulk hard-delete this data contract and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the data contract", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java index 200170085ec4..f5bdfed70095 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java @@ -715,6 +715,28 @@ public Response updateVote( .toResponse(); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDatabasePrefixHard", + summary = "Hard-delete a database and all descendants by FQN prefix", + description = + "Bulk hard-delete this database and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the database", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java index 7be0e5fa9441..f973f9b937ce 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java @@ -778,6 +778,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDatabaseSchemaPrefixHard", + summary = "Hard-delete a database schema and all descendants by FQN prefix", + description = + "Bulk hard-delete this database schema and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the database schema", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java index 760da9de6b55..8bd90a3db826 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java @@ -32,6 +32,8 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.PathParam; @Path("/v1/storedProcedures") @Tag( @@ -566,6 +568,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteStoredProcedurePrefixHard", + summary = "Hard-delete a stored procedure and all descendants by FQN prefix", + description = + "Bulk hard-delete this stored procedure and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the stored procedure", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java index fea914d1726f..969623880b8f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java @@ -750,6 +750,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTablePrefixHard", + summary = "Hard-delete a table and all descendants by FQN prefix", + description = + "Bulk hard-delete this table and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the table", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java index 9a28ee188715..1512c573a4e7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java @@ -454,6 +454,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDataInsightChartPrefixHard", + summary = "Hard-delete a data insight chart and all descendants by FQN prefix", + description = + "Bulk hard-delete this data insight chart and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the data insight chart", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java index dbb6468ee460..480d1fe1b1f4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java @@ -305,4 +305,26 @@ public Response stopChartDataStreaming( return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorResponse).build(); } } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDataInsightSystemChartPrefixHard", + summary = "Hard-delete a data insight system chart and all descendants by FQN prefix", + description = + "Bulk hard-delete this data insight system chart and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the data insight system chart", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java index 8a69e00aa088..7b12d357692e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java @@ -588,6 +588,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDashboardDataModelPrefixHard", + summary = "Hard-delete a dashboard data model and all descendants by FQN prefix", + description = + "Bulk hard-delete this dashboard data model and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the dashboard data model", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java index cd5e19efe834..202e0d4e041c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java @@ -452,6 +452,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDocStorePrefixHard", + summary = "Hard-delete a doc store and all descendants by FQN prefix", + description = + "Bulk hard-delete this doc store and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the doc store", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java index 967d470c9ab5..b32bc78b40e1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java @@ -921,6 +921,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDataProductPrefixHard", + summary = "Hard-delete a data product and all descendants by FQN prefix", + description = + "Bulk hard-delete this data product and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the data product", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java index 028df9d98a95..9e9d390a6e4a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java @@ -496,6 +496,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDomainPrefixHard", + summary = "Hard-delete a domain and all descendants by FQN prefix", + description = + "Bulk hard-delete this domain and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the domain", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java index f5140b493459..955725caac22 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java @@ -924,6 +924,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTestCasePrefixHard", + summary = "Hard-delete a test case and all descendants by FQN prefix", + description = + "Bulk hard-delete this test case and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the test case", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java index 923e699ee405..86dc4434e4a6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java @@ -459,6 +459,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTestDefinitionPrefixHard", + summary = "Hard-delete a test definition and all descendants by FQN prefix", + description = + "Bulk hard-delete this test definition and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the test definition", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java index fed7540d002f..a3cff5e84f8c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java @@ -806,6 +806,28 @@ public Response deleteAsync( return repository.deleteLogicalTestSuiteAsync(securityContext, testSuite, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTestSuitePrefixHard", + summary = "Hard-delete a test suite and all descendants by FQN prefix", + description = + "Bulk hard-delete this test suite and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the test suite", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java index 016c1fdef4ab..50353c03aeca 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java @@ -425,6 +425,28 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDirectoryPrefixHard", + summary = "Hard-delete a directory and all descendants by FQN prefix", + description = + "Bulk hard-delete this directory and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the directory", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java index bcbbdd6088a9..37b1564015af 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java @@ -418,6 +418,28 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteFilePrefixHard", + summary = "Hard-delete a file and all descendants by FQN prefix", + description = + "Bulk hard-delete this file and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the file", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java index 28335091455c..f8f60b068c42 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java @@ -429,6 +429,28 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteSpreadsheetPrefixHard", + summary = "Hard-delete a spreadsheet and all descendants by FQN prefix", + description = + "Bulk hard-delete this spreadsheet and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the spreadsheet", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java index c094d0bbb595..2b32247913c6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java @@ -412,6 +412,28 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteWorksheetPrefixHard", + summary = "Hard-delete a worksheet and all descendants by FQN prefix", + description = + "Bulk hard-delete this worksheet and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the worksheet", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java index 48886f978a64..34dd29e9a4a3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java @@ -635,6 +635,28 @@ public Response delete( return super.delete(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteNotificationTemplatePrefixHard", + summary = "Hard-delete a notification template and all descendants by FQN prefix", + description = + "Bulk hard-delete this notification template and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the notification template", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java index 9833213eeacd..a1dc2e044b08 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java @@ -561,6 +561,28 @@ public Response deleteEventSubscriptionAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteEventSubscriptionPrefixHard", + summary = "Hard-delete a event subscription and all descendants by FQN prefix", + description = + "Bulk hard-delete this event subscription and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the event subscription", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java index 57b8c03e391f..44527e414c3d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java @@ -474,6 +474,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteGlossaryPrefixHard", + summary = "Hard-delete a glossary and all descendants by FQN prefix", + description = + "Bulk hard-delete this glossary and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the glossary", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java index e7e2eef75782..065164c95381 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java @@ -1046,6 +1046,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteGlossaryTermPrefixHard", + summary = "Hard-delete a glossary term and all descendants by FQN prefix", + description = + "Bulk hard-delete this glossary term and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the glossary term", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java index e52a68bd1c84..6cecca071ca0 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java @@ -484,6 +484,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteWorkflowDefinitionPrefixHard", + summary = "Hard-delete a workflow definition and all descendants by FQN prefix", + description = + "Bulk hard-delete this workflow definition and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the workflow definition", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java index e8d345599fb6..9bc32606db50 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java @@ -379,6 +379,28 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, kpi); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteKpiPrefixHard", + summary = "Hard-delete a KPI and all descendants by FQN prefix", + description = + "Bulk hard-delete this KPI and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the KPI", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java index 3874474e37cd..935976ed0094 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java @@ -451,4 +451,26 @@ private LearningResource toEntity(CreateLearningResource create, String updatedB : LearningResource.Status.fromValue(create.getStatus().value())); return resource; } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteLearningResourcePrefixHard", + summary = "Hard-delete a learning resource and all descendants by FQN prefix", + description = + "Bulk hard-delete this learning resource and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the learning resource", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java index 9865a2218bd3..e6d1ee387849 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java @@ -541,6 +541,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMetricPrefixHard", + summary = "Hard-delete a metric and all descendants by FQN prefix", + description = + "Bulk hard-delete this metric and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the metric", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java index 785c2358cba3..e695d5f2140a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java @@ -584,6 +584,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMlModelPrefixHard", + summary = "Hard-delete a ML model and all descendants by FQN prefix", + description = + "Bulk hard-delete this ML model and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the ML model", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java index c307cdd2e5e3..25f4f9f4a871 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java @@ -1218,6 +1218,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePipelinePrefixHard", + summary = "Hard-delete a pipeline and all descendants by FQN prefix", + description = + "Bulk hard-delete this pipeline and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the pipeline", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java index 80c59d85b093..04266978ff27 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java @@ -506,6 +506,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePolicyPrefixHard", + summary = "Hard-delete a policy and all descendants by FQN prefix", + description = + "Bulk hard-delete this policy and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the policy", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java index 695855121df7..9ce376029ce7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java @@ -697,6 +697,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteQueryPrefixHard", + summary = "Hard-delete a query and all descendants by FQN prefix", + description = + "Bulk hard-delete this query and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the query", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java index 3f0fef5416bc..c84f8e845ee5 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java @@ -52,6 +52,7 @@ import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; import org.openmetadata.service.util.EntityUtil.Fields; +import jakarta.ws.rs.DELETE; @Path("/v1/reports") @Tag( @@ -227,4 +228,26 @@ private void addToReport(SecurityContext securityContext, Report report) { .withUpdatedBy(securityContext.getUserPrincipal().getName()) .withUpdatedAt(System.currentTimeMillis()); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteReportPrefixHard", + summary = "Hard-delete a report and all descendants by FQN prefix", + description = + "Bulk hard-delete this report and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the report", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java index 2570ba07642a..cd779ac0b1b1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java @@ -663,6 +663,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteSearchIndexPrefixHard", + summary = "Hard-delete a search index and all descendants by FQN prefix", + description = + "Bulk hard-delete this search index and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the search index", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java index b8555d192487..aab45e24818e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java @@ -522,6 +522,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteApiServicePrefixHard", + summary = "Hard-delete a API service and all descendants by FQN prefix", + description = + "Bulk hard-delete this API service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the API service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java index b13a03e7a461..32086353837f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java @@ -37,6 +37,7 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import jakarta.ws.rs.DELETE; @Slf4j @Path("/v1/services/testConnectionDefinitions") @@ -217,4 +218,26 @@ public TestConnectionDefinition getByName( Include include) { return getByNameInternal(uriInfo, securityContext, name, fieldsParam, include); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTestConnectionDefinitionPrefixHard", + summary = "Hard-delete a test connection definition and all descendants by FQN prefix", + description = + "Bulk hard-delete this test connection definition and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the test connection definition", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java index 5ebcc81c1391..9d278f3ecb09 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java @@ -573,6 +573,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDashboardServicePrefixHard", + summary = "Hard-delete a dashboard service and all descendants by FQN prefix", + description = + "Bulk hard-delete this dashboard service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the dashboard service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java index fa3b7c45231c..bdce3a1fc4b7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java @@ -650,6 +650,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteDriveServicePrefixHard", + summary = "Hard-delete a drive service and all descendants by FQN prefix", + description = + "Bulk hard-delete this drive service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the drive service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java index 9216cdd4eb2b..6776e3c32e58 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java @@ -904,6 +904,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteIngestionPipelinePrefixHard", + summary = "Hard-delete a ingestion pipeline and all descendants by FQN prefix", + description = + "Bulk hard-delete this ingestion pipeline and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the ingestion pipeline", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java index f2fe8332044e..a79bf13b6d13 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java @@ -575,6 +575,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteLlmServicePrefixHard", + summary = "Hard-delete a LLM service and all descendants by FQN prefix", + description = + "Bulk hard-delete this LLM service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the LLM service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java index 826b5505f385..c4c82901efc0 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java @@ -568,6 +568,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMcpServicePrefixHard", + summary = "Hard-delete a MCP service and all descendants by FQN prefix", + description = + "Bulk hard-delete this MCP service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the MCP service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java index 5ac5acc91193..c450ee59a0fa 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java @@ -576,6 +576,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMessagingServicePrefixHard", + summary = "Hard-delete a messaging service and all descendants by FQN prefix", + description = + "Bulk hard-delete this messaging service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the messaging service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java index 9cab5010d6d8..3185e8e2d15f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java @@ -616,6 +616,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMetadataServicePrefixHard", + summary = "Hard-delete a metadata service and all descendants by FQN prefix", + description = + "Bulk hard-delete this metadata service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the metadata service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java index 1568140a2bf4..976705983d24 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java @@ -589,6 +589,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteMlModelServicePrefixHard", + summary = "Hard-delete a ML model service and all descendants by FQN prefix", + description = + "Bulk hard-delete this ML model service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the ML model service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java index c8d79e38a3b5..811d3659e483 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java @@ -589,6 +589,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePipelineServicePrefixHard", + summary = "Hard-delete a pipeline service and all descendants by FQN prefix", + description = + "Bulk hard-delete this pipeline service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the pipeline service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java index 7d3b9adb9b6f..0ca7c99f9888 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java @@ -575,6 +575,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteSearchIndexServicePrefixHard", + summary = "Hard-delete a search index service and all descendants by FQN prefix", + description = + "Bulk hard-delete this search index service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the search index service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java index 1b86dd974a49..e640b1d56311 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java @@ -700,6 +700,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteSecurityServicePrefixHard", + summary = "Hard-delete a security service and all descendants by FQN prefix", + description = + "Bulk hard-delete this security service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the security service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java index a9e24a8bdfc5..de1d16033079 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java @@ -573,6 +573,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteStorageServicePrefixHard", + summary = "Hard-delete a storage service and all descendants by FQN prefix", + description = + "Bulk hard-delete this storage service and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the storage service", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java index a2e65b5beb10..72d4941497c2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java @@ -584,6 +584,28 @@ public Response updateVote( .toResponse(); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteContainerPrefixHard", + summary = "Hard-delete a container and all descendants by FQN prefix", + description = + "Bulk hard-delete this container and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the container", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java index 89ebc8ab3f30..ccd8603d3e59 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java @@ -437,6 +437,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteClassificationPrefixHard", + summary = "Hard-delete a classification and all descendants by FQN prefix", + description = + "Bulk hard-delete this classification and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the classification", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java index 00f943464369..b39b22d386f8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java @@ -517,6 +517,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTagPrefixHard", + summary = "Hard-delete a tag and all descendants by FQN prefix", + description = + "Bulk hard-delete this tag and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the tag", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java index 3c9b4cd8a2c6..597aeac0918b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java @@ -47,6 +47,8 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.PathParam; @Slf4j @Path("/v1/personas") @@ -414,6 +416,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deletePersonaPrefixHard", + summary = "Hard-delete a persona and all descendants by FQN prefix", + description = + "Bulk hard-delete this persona and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the persona", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java index 5f9dde00c015..6923bcaf1acf 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java @@ -475,6 +475,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteRolePrefixHard", + summary = "Hard-delete a role and all descendants by FQN prefix", + description = + "Bulk hard-delete this role and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the role", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java index c5143f25fa32..f9ed4ba79e2c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java @@ -662,6 +662,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTeamPrefixHard", + summary = "Hard-delete a team and all descendants by FQN prefix", + description = + "Bulk hard-delete this team and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the team", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java index c1610ad70230..bd14b6916afc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java @@ -1127,6 +1127,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteUserPrefixHard", + summary = "Hard-delete a user and all descendants by FQN prefix", + description = + "Bulk hard-delete this user and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the user", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java index 6b092a86f0c8..be82d40100f7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java @@ -631,6 +631,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTopicPrefixHard", + summary = "Hard-delete a topic and all descendants by FQN prefix", + description = + "Bulk hard-delete this topic and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the topic", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{fqn}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java index 9546182a40b0..1fb20451aaca 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java @@ -481,6 +481,28 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } + + @DELETE + @Path("/prefix/{id}") + @Operation( + operationId = "deleteTypePrefixHard", + summary = "Hard-delete a type and all descendants by FQN prefix", + description = + "Bulk hard-delete this type and all descendants whose FQN starts with this " + + "entity's FQN. Significantly faster than recursive delete for large hierarchies.", + responses = { + @ApiResponse(responseCode = "202", description = "Deletion accepted and running"), + @ApiResponse(responseCode = "404", description = "Entity for instance {id} is not found") + }) + public Response deletePrefixHardById( + @Context UriInfo uriInfo, + @Context SecurityContext securityContext, + @Parameter(description = "Id of the type", schema = @Schema(type = "UUID")) + @PathParam("id") + UUID id) { + return deletePrefixHardById(uriInfo, securityContext, id); + } + @DELETE @Path("/name/{name}") @Operation( From a570eb1bc0d9ddc06a3335c80cfc5892a7872854 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Thu, 9 Apr 2026 22:43:20 +0530 Subject: [PATCH 21/29] fix: add missing UUID and Response imports in two resource classes DataInsightSystemChartResource was missing java.util.UUID and TestConnectionDefinitionResource was missing jakarta.ws.rs.core.Response, both required by the new deletePrefixHardById endpoint. Spotted during mvn clean package compilation. Co-Authored-By: Claude Sonnet 4.6 --- .../factories/DatabaseSchemaTestFactory.java | 3 +- .../it/tests/PrefixDeletionBenchmarkIT.java | 99 +++++++---- .../it/tests/PrefixDeletionIT.java | 165 ++++++++++++------ .../migration/utils/v1130/MigrationUtil.java | 7 +- .../service/resources/EntityResource.java | 7 +- .../resources/ai/AIApplicationResource.java | 1 - .../ai/AIGovernancePolicyResource.java | 1 - .../resources/ai/LLMModelResource.java | 1 - .../resources/ai/McpServerResource.java | 1 - .../resources/ai/PromptTemplateResource.java | 1 - .../analytics/WebAnalyticEventResource.java | 1 - .../resources/apis/APICollectionResource.java | 1 - .../resources/apis/APIEndpointResource.java | 1 - .../apps/AppMarketPlaceResource.java | 5 +- .../service/resources/apps/AppResource.java | 4 +- .../automations/WorkflowResource.java | 1 - .../service/resources/bots/BotResource.java | 4 +- .../resources/charts/ChartResource.java | 4 +- .../dashboards/DashboardResource.java | 1 - .../resources/data/DataContractResource.java | 1 - .../resources/databases/DatabaseResource.java | 1 - .../databases/DatabaseSchemaResource.java | 1 - .../databases/StoredProcedureResource.java | 5 +- .../resources/databases/TableResource.java | 4 +- .../datainsight/DataInsightChartResource.java | 1 - .../DataInsightSystemChartResource.java | 6 +- .../DashboardDataModelResource.java | 1 - .../resources/docstore/DocStoreResource.java | 1 - .../domains/DataProductResource.java | 1 - .../resources/domains/DomainResource.java | 4 +- .../resources/dqtests/TestCaseResource.java | 1 - .../dqtests/TestDefinitionResource.java | 1 - .../resources/dqtests/TestSuiteResource.java | 1 - .../resources/drives/DirectoryResource.java | 1 - .../resources/drives/FileResource.java | 4 +- .../resources/drives/SpreadsheetResource.java | 1 - .../resources/drives/WorksheetResource.java | 1 - .../events/NotificationTemplateResource.java | 1 - .../EventSubscriptionResource.java | 1 - .../resources/glossary/GlossaryResource.java | 1 - .../glossary/GlossaryTermResource.java | 1 - .../WorkflowDefinitionResource.java | 1 - .../service/resources/kpi/KpiResource.java | 4 +- .../learning/LearningResourceResource.java | 1 - .../resources/metrics/MetricResource.java | 4 +- .../resources/mlmodels/MlModelResource.java | 1 - .../resources/pipelines/PipelineResource.java | 1 - .../resources/policies/PolicyResource.java | 4 +- .../resources/query/QueryResource.java | 4 +- .../resources/reports/ReportResource.java | 6 +- .../searchindex/SearchIndexResource.java | 1 - .../apiservices/APIServiceResource.java | 1 - .../TestConnectionDefinitionResource.java | 8 +- .../dashboard/DashboardServiceResource.java | 1 - .../services/drive/DriveServiceResource.java | 1 - .../IngestionPipelineResource.java | 1 - .../services/llm/LLMServiceResource.java | 1 - .../services/mcp/McpServiceResource.java | 1 - .../messaging/MessagingServiceResource.java | 1 - .../metadata/MetadataServiceResource.java | 1 - .../mlmodel/MlModelServiceResource.java | 1 - .../pipeline/PipelineServiceResource.java | 1 - .../searchIndexes/SearchServiceResource.java | 1 - .../security/SecurityServiceResource.java | 1 - .../storage/StorageServiceResource.java | 1 - .../resources/storages/ContainerResource.java | 1 - .../tags/ClassificationResource.java | 1 - .../service/resources/tags/TagResource.java | 4 +- .../resources/teams/PersonaResource.java | 5 +- .../service/resources/teams/RoleResource.java | 4 +- .../service/resources/teams/TeamResource.java | 4 +- .../service/resources/teams/UserResource.java | 4 +- .../resources/topics/TopicResource.java | 4 +- .../service/resources/types/TypeResource.java | 4 +- 74 files changed, 216 insertions(+), 211 deletions(-) diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java index a8a9bf96e0b3..b767160728aa 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/factories/DatabaseSchemaTestFactory.java @@ -52,7 +52,8 @@ public static DatabaseSchema createSimple(TestNamespace ns) { /** * Create a schema with a namespaced base name using fluent API. */ - public static DatabaseSchema createWithName(TestNamespace ns, String databaseFqn, String baseName) { + public static DatabaseSchema createWithName( + TestNamespace ns, String databaseFqn, String baseName) { return DatabaseSchemas.create().name(ns.prefix(baseName)).in(databaseFqn).execute(); } diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java index 8c2351b3c388..cb02ea0d376d 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionBenchmarkIT.java @@ -91,12 +91,15 @@ static void setup() { @Test void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throws Exception { int totalTables = DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; - int totalEntities = 1 + DATABASES_PER_SERVICE - + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE - + totalTables; + int totalEntities = + 1 + DATABASES_PER_SERVICE + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE + totalTables; LOG.info( "Benchmark topology: {} databases × {} schemas × {} tables = {} tables, {} total entities per service", - DATABASES_PER_SERVICE, SCHEMAS_PER_DATABASE, TABLES_PER_SCHEMA, totalTables, totalEntities); + DATABASES_PER_SERVICE, + SCHEMAS_PER_DATABASE, + TABLES_PER_SCHEMA, + totalTables, + totalEntities); DatabaseService oldService = buildHierarchy(ns, "old"); long oldMs = timeOldDelete(oldService); @@ -113,11 +116,17 @@ void benchmark_oldRecursiveHardDelete_vs_newPrefixDelete(TestNamespace ns) throw private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - int totalEntities = 1 + DATABASES_PER_SERVICE - + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE - + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; - LOG.info("[{}] Seeding {} entities under service {} using {} threads ...", - tag, totalEntities, service.getName(), SEED_THREADS); + int totalEntities = + 1 + + DATABASES_PER_SERVICE + + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE + + DATABASES_PER_SERVICE * SCHEMAS_PER_DATABASE * TABLES_PER_SCHEMA; + LOG.info( + "[{}] Seeding {} entities under service {} using {} threads ...", + tag, + totalEntities, + service.getName(), + SEED_THREADS); long seedStart = System.currentTimeMillis(); ExecutorService pool = Executors.newFixedThreadPool(SEED_THREADS); @@ -125,8 +134,11 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exce List> dbFutures = new ArrayList<>(); for (int d = 0; d < DATABASES_PER_SERVICE; d++) { final int dIdx = d; - dbFutures.add(pool.submit(() -> - DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), tag + "db" + dIdx))); + dbFutures.add( + pool.submit( + () -> + DatabaseTestFactory.createWithName( + ns, service.getFullyQualifiedName(), tag + "db" + dIdx))); } List databases = new ArrayList<>(); for (Future f : dbFutures) { @@ -139,8 +151,11 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exce final int dIdx = d; for (int s = 0; s < SCHEMAS_PER_DATABASE; s++) { final int sIdx = s; - schemaFutures.add(pool.submit(() -> - DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), tag + "sc" + dIdx + "x" + sIdx))); + schemaFutures.add( + pool.submit( + () -> + DatabaseSchemaTestFactory.createWithName( + ns, database.getFullyQualifiedName(), tag + "sc" + dIdx + "x" + sIdx))); } } List schemas = new ArrayList<>(); @@ -154,10 +169,13 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exce final int sIdx = s; for (int t = 0; t < TABLES_PER_SCHEMA; t++) { final int tIdx = t; - tableFutures.add(pool.submit(() -> { - TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), tag + "tbl" + sIdx + "x" + tIdx); - return null; - })); + tableFutures.add( + pool.submit( + () -> { + TableTestFactory.createWithName( + ns, schema.getFullyQualifiedName(), tag + "tbl" + sIdx + "x" + tIdx); + return null; + })); } } for (Future f : tableFutures) { @@ -169,8 +187,11 @@ private DatabaseService buildHierarchy(TestNamespace ns, String tag) throws Exce } long seedMs = System.currentTimeMillis() - seedStart; - LOG.info("[{}] Hierarchy seeded in {} ms ({} ms/entity avg)", - tag, seedMs, seedMs / Math.max(totalEntities, 1)); + LOG.info( + "[{}] Hierarchy seeded in {} ms ({} ms/entity avg)", + tag, + seedMs, + seedMs / Math.max(totalEntities, 1)); return service; } @@ -178,9 +199,11 @@ private long timeOldDelete(DatabaseService service) throws Exception { LOG.info("Timing OLD recursive hard delete for service {} ...", service.getName()); long start = System.currentTimeMillis(); - String url = SdkClients.getServerUrl() - + "/v1/services/databaseServices/" + service.getId() - + "?hardDelete=true&recursive=true"; + String url = + SdkClients.getServerUrl() + + "/v1/services/databaseServices/" + + service.getId() + + "?hardDelete=true&recursive=true"; sendDelete(url); long elapsed = System.currentTimeMillis() - start; @@ -192,8 +215,8 @@ private long timeNewDelete(DatabaseService service) throws Exception { LOG.info("Timing NEW FQN prefix hard delete for service {} ...", service.getName()); long start = System.currentTimeMillis(); - String url = SdkClients.getServerUrl() - + "/v1/services/databaseServices/prefix/" + service.getId(); + String url = + SdkClients.getServerUrl() + "/v1/services/databaseServices/prefix/" + service.getId(); sendDelete(url); // Prefix delete is async — poll until the service is actually gone so we measure @@ -202,14 +225,15 @@ private long timeNewDelete(DatabaseService service) throws Exception { Awaitility.await("Wait for prefix deletion of " + service.getName() + " to complete") .atMost(DELETE_POLL_TIMEOUT) .pollInterval(DELETE_POLL_INTERVAL) - .until(() -> { - try { - SdkClients.adminClient().databaseServices().get(serviceId.toString()); - return false; - } catch (Exception e) { - return true; - } - }); + .until( + () -> { + try { + SdkClients.adminClient().databaseServices().get(serviceId.toString()); + return false; + } catch (Exception e) { + return true; + } + }); long elapsed = System.currentTimeMillis() - start; LOG.info("NEW FQN prefix hard delete completed in {} ms", elapsed); @@ -217,11 +241,12 @@ private long timeNewDelete(DatabaseService service) throws Exception { } private void sendDelete(String url) throws Exception { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Authorization", "Bearer " + SdkClients.getAdminToken()) - .DELETE() - .build(); + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Authorization", "Bearer " + SdkClients.getAdminToken()) + .DELETE() + .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() >= 300) { diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java index 5b0e6a7ffc52..7d2b51d59086 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PrefixDeletionIT.java @@ -70,8 +70,10 @@ static void setup() { @Test void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); - DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sc"); + Database database = + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); + DatabaseSchema schema = + DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sc"); List tableIds = new ArrayList<>(); for (int i = 0; i < 5; i++) { @@ -81,9 +83,13 @@ void prefixDelete_service_removesServiceAndAllDescendants(TestNamespace ns) thro prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); - awaitGone("service", () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); - awaitGone("database", () -> SdkClients.adminClient().databases().get(database.getId().toString())); - awaitGone("schema", () -> SdkClients.adminClient().databaseSchemas().get(schema.getId().toString())); + awaitGone( + "service", + () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); + awaitGone( + "database", () -> SdkClients.adminClient().databases().get(database.getId().toString())); + awaitGone( + "schema", () -> SdkClients.adminClient().databaseSchemas().get(schema.getId().toString())); for (UUID tableId : tableIds) { awaitGone("table", () -> SdkClients.adminClient().tables().get(tableId.toString())); } @@ -98,13 +104,17 @@ void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) thro List tableIds = new ArrayList<>(); for (int d = 0; d < 2; d++) { - Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db" + d); + Database database = + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db" + d); dbIds.add(database.getId()); for (int s = 0; s < 2; s++) { - DatabaseSchema schema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sc" + d + s); + DatabaseSchema schema = + DatabaseSchemaTestFactory.createWithName( + ns, database.getFullyQualifiedName(), "sc" + d + s); schemaIds.add(schema.getId()); for (int t = 0; t < 3; t++) { - Table table = TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + d + s + t); + Table table = + TableTestFactory.createWithName(ns, schema.getFullyQualifiedName(), "t" + d + s + t); tableIds.add(table.getId()); } } @@ -112,7 +122,9 @@ void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) thro prefixDelete("/v1/services/databaseServices/prefix/", service.getId()); - awaitGone("service", () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); + awaitGone( + "service", + () -> SdkClients.adminClient().databaseServices().get(service.getId().toString())); for (UUID id : dbIds) { awaitGone("database", () -> SdkClients.adminClient().databases().get(id.toString())); } @@ -127,73 +139,119 @@ void prefixDelete_service_withMultipleDatabasesAndSchemas(TestNamespace ns) thro // ── Database-level ──────────────────────────────────────────────────────────── @Test - void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIntact(TestNamespace ns) throws Exception { + void prefixDelete_database_removesDatabaseAndDescendantsLeavingSiblingDatabaseIntact( + TestNamespace ns) throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database targetDb = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "tgtdb"); - DatabaseSchema targetSchema = DatabaseSchemaTestFactory.createWithName(ns, targetDb.getFullyQualifiedName(), "tgtsc"); - Table targetTable = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgttbl"); + Database targetDb = + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "tgtdb"); + DatabaseSchema targetSchema = + DatabaseSchemaTestFactory.createWithName(ns, targetDb.getFullyQualifiedName(), "tgtsc"); + Table targetTable = + TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgttbl"); - Database siblingDb = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "sibdb"); - DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.createWithName(ns, siblingDb.getFullyQualifiedName(), "sibsc"); - Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); + Database siblingDb = + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "sibdb"); + DatabaseSchema siblingSchema = + DatabaseSchemaTestFactory.createWithName(ns, siblingDb.getFullyQualifiedName(), "sibsc"); + Table siblingTable = + TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); prefixDelete("/v1/databases/prefix/", targetDb.getId()); - awaitGone("target database", () -> SdkClients.adminClient().databases().get(targetDb.getId().toString())); - awaitGone("target schema", () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); - awaitGone("target table", () -> SdkClients.adminClient().tables().get(targetTable.getId().toString())); - - assertNotNull(SdkClients.adminClient().databases().get(siblingDb.getId().toString()), "sibling database should survive"); - assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); - assertNotNull(SdkClients.adminClient().tables().get(siblingTable.getId().toString()), "sibling table should survive"); - assertNotNull(SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service should survive"); + awaitGone( + "target database", + () -> SdkClients.adminClient().databases().get(targetDb.getId().toString())); + awaitGone( + "target schema", + () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); + awaitGone( + "target table", + () -> SdkClients.adminClient().tables().get(targetTable.getId().toString())); + + assertNotNull( + SdkClients.adminClient().databases().get(siblingDb.getId().toString()), + "sibling database should survive"); + assertNotNull( + SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), + "sibling schema should survive"); + assertNotNull( + SdkClients.adminClient().tables().get(siblingTable.getId().toString()), + "sibling table should survive"); + assertNotNull( + SdkClients.adminClient().databaseServices().get(service.getId().toString()), + "service should survive"); } // ── Schema-level ───────────────────────────────────────────────────────────── @Test - void prefixDelete_schema_removesSchemaAndTablesLeavingSiblingSchemaIntact(TestNamespace ns) throws Exception { + void prefixDelete_schema_removesSchemaAndTablesLeavingSiblingSchemaIntact(TestNamespace ns) + throws Exception { DatabaseService service = DatabaseServiceTestFactory.createPostgres(ns); - Database database = DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); + Database database = + DatabaseTestFactory.createWithName(ns, service.getFullyQualifiedName(), "db"); - DatabaseSchema targetSchema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "tgtsc"); + DatabaseSchema targetSchema = + DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "tgtsc"); List targetTableIds = new ArrayList<>(); for (int i = 0; i < 3; i++) { - Table table = TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt" + i); + Table table = + TableTestFactory.createWithName(ns, targetSchema.getFullyQualifiedName(), "tgt" + i); targetTableIds.add(table.getId()); } - DatabaseSchema siblingSchema = DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sibsc"); - Table siblingTable = TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); + DatabaseSchema siblingSchema = + DatabaseSchemaTestFactory.createWithName(ns, database.getFullyQualifiedName(), "sibsc"); + Table siblingTable = + TableTestFactory.createWithName(ns, siblingSchema.getFullyQualifiedName(), "sibtbl"); prefixDelete("/v1/databaseSchemas/prefix/", targetSchema.getId()); - awaitGone("target schema", () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); + awaitGone( + "target schema", + () -> SdkClients.adminClient().databaseSchemas().get(targetSchema.getId().toString())); for (UUID tableId : targetTableIds) { awaitGone("target table", () -> SdkClients.adminClient().tables().get(tableId.toString())); } - assertNotNull(SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), "sibling schema should survive"); - assertNotNull(SdkClients.adminClient().tables().get(siblingTable.getId().toString()), "sibling table should survive"); - assertNotNull(SdkClients.adminClient().databases().get(database.getId().toString()), "database should survive"); - assertNotNull(SdkClients.adminClient().databaseServices().get(service.getId().toString()), "service should survive"); + assertNotNull( + SdkClients.adminClient().databaseSchemas().get(siblingSchema.getId().toString()), + "sibling schema should survive"); + assertNotNull( + SdkClients.adminClient().tables().get(siblingTable.getId().toString()), + "sibling table should survive"); + assertNotNull( + SdkClients.adminClient().databases().get(database.getId().toString()), + "database should survive"); + assertNotNull( + SdkClients.adminClient().databaseServices().get(service.getId().toString()), + "service should survive"); } // ── Helpers ─────────────────────────────────────────────────────────────────── private void prefixDelete(String path, UUID id) throws Exception { String url = SdkClients.getServerUrl() + path + id; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Authorization", "Bearer " + SdkClients.getAdminToken()) - .DELETE() - .build(); - - HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(202, response.statusCode(), - "Expected 202 Accepted from prefix delete " + path + id - + ", got " + response.statusCode() + ": " + response.body()); + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Authorization", "Bearer " + SdkClients.getAdminToken()) + .DELETE() + .build(); + + HttpResponse response = + HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + assertEquals( + 202, + response.statusCode(), + "Expected 202 Accepted from prefix delete " + + path + + id + + ", got " + + response.statusCode() + + ": " + + response.body()); } /** @@ -204,14 +262,15 @@ private void awaitGone(String entityType, ThrowingSupplier fetch) { Awaitility.await("Wait for " + entityType + " to be deleted") .atMost(DELETE_TIMEOUT) .pollInterval(POLL_INTERVAL) - .until(() -> { - try { - fetch.get(); - return false; - } catch (Exception e) { - return true; - } - }); + .until( + () -> { + try { + fetch.get(); + return false; + } catch (Exception e) { + return true; + } + }); } @FunctionalInterface diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java index f0bb04becc53..d6ab1addbc73 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1130/MigrationUtil.java @@ -28,7 +28,8 @@ public static void backfillRelationshipFqnHashes(Handle handle) { try { backfillForEntityType(handle, entityType); } catch (Exception e) { - LOG.warn("Failed to backfill FQN hashes for entity type {}: {}", entityType, e.getMessage()); + LOG.warn( + "Failed to backfill FQN hashes for entity type {}: {}", entityType, e.getMessage()); } } } @@ -45,7 +46,9 @@ private static void backfillForEntityType(Handle handle, String entityType) { if (fromUpdated + toUpdated > 0) { LOG.info( "Backfilled FQN hashes for entity type {}: {} fromFQNHash, {} toFQNHash", - entityType, fromUpdated, toUpdated); + entityType, + fromUpdated, + toUpdated); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java index ea2eda5057b9..a8f3c21945d9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java @@ -29,11 +29,9 @@ import jakarta.json.JsonPatch; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; -import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -707,10 +705,7 @@ public Response deleteByIdAsync( return response; } - public Response deletePrefixHardById( - UriInfo uriInfo, - SecurityContext securityContext, - UUID id) { + public Response deletePrefixHardById(UriInfo uriInfo, SecurityContext securityContext, UUID id) { String jobId = UUID.randomUUID().toString(); OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE); authorizer.authorize( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java index fcdc25c5732a..ed0cf186179e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java @@ -510,7 +510,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java index 9634159ab461..aba68ae768d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java @@ -497,7 +497,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java index cce2bfa208aa..fe609e532828 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java @@ -477,7 +477,6 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java index 37dceaf5c24a..d96848d829fe 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java @@ -531,7 +531,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java index bafff12cd31e..cba19c0ee01e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java @@ -494,7 +494,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java index d60b63ee5f2c..0a6db828fd09 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java @@ -352,7 +352,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java index 44b35fdf1c73..91f27554b0ed 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java @@ -545,7 +545,6 @@ public Response updateVote( .toResponse(); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java index d09c56988dba..c316b9ce3fd8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java @@ -591,7 +591,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java index cc314dbadb7b..9ff71de30065 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java @@ -374,7 +374,6 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, app); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -390,7 +389,9 @@ public Response createOrUpdate( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the app marketplace definition", schema = @Schema(type = "UUID")) + @Parameter( + description = "Id of the app marketplace definition", + schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java index 08d350fdc2dc..dd2b3858bbef 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java @@ -921,7 +921,6 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, app); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -937,8 +936,7 @@ public Response createOrUpdate( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the app", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the app", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java index 8c8239431804..94834140b4b8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java @@ -529,7 +529,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java index 18ef3037adb7..fae9927ed868 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java @@ -435,7 +435,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -451,8 +450,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the bot", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the bot", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java index da599956deff..ff151b75a744 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java @@ -530,7 +530,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -546,8 +545,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the chart", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the chart", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java index 477c7c1706f3..2084adbc16a4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java @@ -575,7 +575,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java index acacf588dd2d..9f7d76979fac 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java @@ -550,7 +550,6 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java index f5bdfed70095..1f18f8ecb141 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java @@ -715,7 +715,6 @@ public Response updateVote( .toResponse(); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java index f973f9b937ce..b036cf8db38e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java @@ -778,7 +778,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java index 8bd90a3db826..5c46056ee416 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java @@ -14,6 +14,8 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.ws.rs.*; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.core.*; import java.util.List; import java.util.UUID; @@ -32,8 +34,6 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.PathParam; @Path("/v1/storedProcedures") @Tag( @@ -568,7 +568,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java index 969623880b8f..50ca02980ce5 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java @@ -750,7 +750,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -766,8 +765,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the table", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the table", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java index 1512c573a4e7..dd37d4266ab9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java @@ -454,7 +454,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java index 480d1fe1b1f4..050c5210eebc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.openmetadata.schema.dataInsight.DataInsightChart; import org.openmetadata.schema.dataInsight.custom.DataInsightCustomChart; import org.openmetadata.schema.dataInsight.custom.DataInsightCustomChartResultList; @@ -321,10 +322,11 @@ public Response stopChartDataStreaming( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the data insight system chart", schema = @Schema(type = "UUID")) + @Parameter( + description = "Id of the data insight system chart", + schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } - } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java index 7b12d357692e..c2c84d7ae3f7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java @@ -588,7 +588,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java index 202e0d4e041c..a177ef28a8d0 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java @@ -452,7 +452,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java index b32bc78b40e1..44e8651a652f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java @@ -921,7 +921,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java index 9e9d390a6e4a..18ecbd4c1cdd 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java @@ -496,7 +496,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -512,8 +511,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the domain", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the domain", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java index 955725caac22..b87288209c66 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java @@ -924,7 +924,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java index 86dc4434e4a6..58e108093284 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java @@ -459,7 +459,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java index a3cff5e84f8c..1b173c4ff091 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java @@ -806,7 +806,6 @@ public Response deleteAsync( return repository.deleteLogicalTestSuiteAsync(securityContext, testSuite, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java index 50353c03aeca..bcd3be2d0b3d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java @@ -425,7 +425,6 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java index 37b1564015af..9e125be16aad 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java @@ -418,7 +418,6 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -434,8 +433,7 @@ public Response delete( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the file", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the file", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java index f8f60b068c42..26f5a56efb74 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java @@ -429,7 +429,6 @@ public Response delete( return delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java index 2b32247913c6..565c579bd1e8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java @@ -412,7 +412,6 @@ public Response delete( return delete(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java index 34dd29e9a4a3..bb3ae535cee1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java @@ -635,7 +635,6 @@ public Response delete( return super.delete(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java index a1dc2e044b08..f4e8b20cd2f2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java @@ -561,7 +561,6 @@ public Response deleteEventSubscriptionAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, true); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java index 44527e414c3d..2a1d4e4a3d5c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java @@ -474,7 +474,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java index 065164c95381..51293cbcd839 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java @@ -1046,7 +1046,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java index 6cecca071ca0..774caf876e84 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java @@ -484,7 +484,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java index 9bc32606db50..8919c34521ca 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java @@ -379,7 +379,6 @@ public Response createOrUpdate( return createOrUpdate(uriInfo, securityContext, kpi); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -395,8 +394,7 @@ public Response createOrUpdate( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the KPI", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the KPI", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java index 935976ed0094..796d334f4add 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java @@ -472,5 +472,4 @@ public Response deletePrefixHardById( UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } - } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java index e6d1ee387849..e829c44577b0 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java @@ -541,7 +541,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -557,8 +556,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the metric", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the metric", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java index e695d5f2140a..0a049fa932a9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java @@ -584,7 +584,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java index 25f4f9f4a871..52e0b9e65f90 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java @@ -1218,7 +1218,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java index 04266978ff27..06294a12619d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java @@ -506,7 +506,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -522,8 +521,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the policy", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the policy", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java index 9ce376029ce7..9c89713fd7eb 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java @@ -697,7 +697,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -713,8 +712,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the query", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the query", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java index c84f8e845ee5..ac2bb19af767 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java @@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; @@ -52,7 +53,6 @@ import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; import org.openmetadata.service.util.EntityUtil.Fields; -import jakarta.ws.rs.DELETE; @Path("/v1/reports") @Tag( @@ -244,10 +244,8 @@ private void addToReport(SecurityContext securityContext, Report report) { public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the report", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the report", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } - } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java index cd779ac0b1b1..7ae2b4ae7582 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java @@ -663,7 +663,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java index aab45e24818e..7cce243eaafc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java @@ -522,7 +522,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java index 32086353837f..d3009e0af1a1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java @@ -12,6 +12,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -20,6 +21,7 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.UriInfo; import java.io.IOException; @@ -37,7 +39,6 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; -import jakarta.ws.rs.DELETE; @Slf4j @Path("/v1/services/testConnectionDefinitions") @@ -234,10 +235,11 @@ public TestConnectionDefinition getByName( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the test connection definition", schema = @Schema(type = "UUID")) + @Parameter( + description = "Id of the test connection definition", + schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } - } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java index 9d278f3ecb09..478c14501dc7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java @@ -573,7 +573,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java index bdce3a1fc4b7..decee536cb1e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java @@ -650,7 +650,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java index 6776e3c32e58..4efab5f48c54 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java @@ -904,7 +904,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java index a79bf13b6d13..4356369cae12 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java @@ -575,7 +575,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java index c4c82901efc0..0f4e78ff7ee4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java @@ -568,7 +568,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java index c450ee59a0fa..937a17d11f3d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java @@ -576,7 +576,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java index 3185e8e2d15f..73a92130d260 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java @@ -616,7 +616,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java index 976705983d24..b509b2d8d13b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java @@ -589,7 +589,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java index 811d3659e483..a08983ed5c1c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java @@ -589,7 +589,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java index 0ca7c99f9888..cb3e3873dfbf 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java @@ -575,7 +575,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java index e640b1d56311..ff42e4ca5a92 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java @@ -700,7 +700,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java index de1d16033079..c132ea9d289d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java @@ -573,7 +573,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java index 72d4941497c2..1f7de022fe1a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java @@ -584,7 +584,6 @@ public Response updateVote( .toResponse(); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java index ccd8603d3e59..0f327eb5a8bc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java @@ -437,7 +437,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java index b39b22d386f8..8367da3ece02 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java @@ -517,7 +517,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -533,8 +532,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the tag", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the tag", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java index 597aeac0918b..dd3ae086446c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java @@ -30,6 +30,8 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.ws.rs.*; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.core.*; import java.util.List; import java.util.UUID; @@ -47,8 +49,6 @@ import org.openmetadata.service.resources.Collection; import org.openmetadata.service.resources.EntityResource; import org.openmetadata.service.security.Authorizer; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.PathParam; @Slf4j @Path("/v1/personas") @@ -416,7 +416,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } - @DELETE @Path("/prefix/{id}") @Operation( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java index 6923bcaf1acf..3c5cf92c3cc4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java @@ -475,7 +475,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, true, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -491,8 +490,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the role", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the role", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java index f9ed4ba79e2c..e7f4b6196c45 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java @@ -662,7 +662,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, recursive, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -678,8 +677,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the team", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the team", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java index bd14b6916afc..59998d47df18 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java @@ -1127,7 +1127,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -1143,8 +1142,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the user", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the user", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java index be82d40100f7..15a8c8f591f6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java @@ -631,7 +631,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, hardDelete); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -647,8 +646,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the topic", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the topic", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java index 1fb20451aaca..3ac6fd1ef027 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java @@ -481,7 +481,6 @@ public Response deleteByIdAsync( return deleteByIdAsync(uriInfo, securityContext, id, false, true); } - @DELETE @Path("/prefix/{id}") @Operation( @@ -497,8 +496,7 @@ public Response deleteByIdAsync( public Response deletePrefixHardById( @Context UriInfo uriInfo, @Context SecurityContext securityContext, - @Parameter(description = "Id of the type", schema = @Schema(type = "UUID")) - @PathParam("id") + @Parameter(description = "Id of the type", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { return deletePrefixHardById(uriInfo, securityContext, id); } From 01be3c6757aabed47f9352077aad5e597f9c2be6 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Fri, 10 Apr 2026 19:43:36 +0530 Subject: [PATCH 22/29] fix: call super.deletePrefixHardById to prevent StackOverflowError The 70 subclass overrides were calling deletePrefixHardById(uriInfo, securityContext, id) which resolved to the overriding method itself (infinite recursion), not the base class implementation. This caused: java.lang.StackOverflowError at DatabaseSchemaResource.deletePrefixHardById(...) at DatabaseSchemaResource.deletePrefixHardById(...) ... Changed all 70 call sites to super.deletePrefixHardById(...) so they delegate to EntityResource's implementation as intended. Co-Authored-By: Claude Sonnet 4.6 --- .../service/resources/ai/AIApplicationResource.java | 2 +- .../service/resources/ai/AIGovernancePolicyResource.java | 2 +- .../org/openmetadata/service/resources/ai/LLMModelResource.java | 2 +- .../openmetadata/service/resources/ai/McpServerResource.java | 2 +- .../service/resources/ai/PromptTemplateResource.java | 2 +- .../service/resources/analytics/WebAnalyticEventResource.java | 2 +- .../service/resources/apis/APICollectionResource.java | 2 +- .../service/resources/apis/APIEndpointResource.java | 2 +- .../service/resources/apps/AppMarketPlaceResource.java | 2 +- .../org/openmetadata/service/resources/apps/AppResource.java | 2 +- .../service/resources/automations/WorkflowResource.java | 2 +- .../org/openmetadata/service/resources/bots/BotResource.java | 2 +- .../openmetadata/service/resources/charts/ChartResource.java | 2 +- .../service/resources/dashboards/DashboardResource.java | 2 +- .../service/resources/data/DataContractResource.java | 2 +- .../service/resources/databases/DatabaseResource.java | 2 +- .../service/resources/databases/DatabaseSchemaResource.java | 2 +- .../service/resources/databases/StoredProcedureResource.java | 2 +- .../openmetadata/service/resources/databases/TableResource.java | 2 +- .../service/resources/datainsight/DataInsightChartResource.java | 2 +- .../datainsight/system/DataInsightSystemChartResource.java | 2 +- .../resources/datamodels/DashboardDataModelResource.java | 2 +- .../service/resources/docstore/DocStoreResource.java | 2 +- .../service/resources/domains/DataProductResource.java | 2 +- .../openmetadata/service/resources/domains/DomainResource.java | 2 +- .../service/resources/dqtests/TestCaseResource.java | 2 +- .../service/resources/dqtests/TestDefinitionResource.java | 2 +- .../service/resources/dqtests/TestSuiteResource.java | 2 +- .../service/resources/drives/DirectoryResource.java | 2 +- .../org/openmetadata/service/resources/drives/FileResource.java | 2 +- .../service/resources/drives/SpreadsheetResource.java | 2 +- .../service/resources/drives/WorksheetResource.java | 2 +- .../service/resources/events/NotificationTemplateResource.java | 2 +- .../events/subscription/EventSubscriptionResource.java | 2 +- .../service/resources/glossary/GlossaryResource.java | 2 +- .../service/resources/glossary/GlossaryTermResource.java | 2 +- .../resources/governance/WorkflowDefinitionResource.java | 2 +- .../org/openmetadata/service/resources/kpi/KpiResource.java | 2 +- .../service/resources/learning/LearningResourceResource.java | 2 +- .../openmetadata/service/resources/metrics/MetricResource.java | 2 +- .../service/resources/mlmodels/MlModelResource.java | 2 +- .../service/resources/pipelines/PipelineResource.java | 2 +- .../openmetadata/service/resources/policies/PolicyResource.java | 2 +- .../org/openmetadata/service/resources/query/QueryResource.java | 2 +- .../openmetadata/service/resources/reports/ReportResource.java | 2 +- .../service/resources/searchindex/SearchIndexResource.java | 2 +- .../resources/services/apiservices/APIServiceResource.java | 2 +- .../services/connections/TestConnectionDefinitionResource.java | 2 +- .../resources/services/dashboard/DashboardServiceResource.java | 2 +- .../resources/services/database/DatabaseServiceResource.java | 2 +- .../service/resources/services/drive/DriveServiceResource.java | 2 +- .../services/ingestionpipelines/IngestionPipelineResource.java | 2 +- .../service/resources/services/llm/LLMServiceResource.java | 2 +- .../service/resources/services/mcp/McpServiceResource.java | 2 +- .../resources/services/messaging/MessagingServiceResource.java | 2 +- .../resources/services/metadata/MetadataServiceResource.java | 2 +- .../resources/services/mlmodel/MlModelServiceResource.java | 2 +- .../resources/services/pipeline/PipelineServiceResource.java | 2 +- .../resources/services/searchIndexes/SearchServiceResource.java | 2 +- .../resources/services/security/SecurityServiceResource.java | 2 +- .../resources/services/storage/StorageServiceResource.java | 2 +- .../service/resources/storages/ContainerResource.java | 2 +- .../service/resources/tags/ClassificationResource.java | 2 +- .../org/openmetadata/service/resources/tags/TagResource.java | 2 +- .../openmetadata/service/resources/teams/PersonaResource.java | 2 +- .../org/openmetadata/service/resources/teams/RoleResource.java | 2 +- .../org/openmetadata/service/resources/teams/TeamResource.java | 2 +- .../org/openmetadata/service/resources/teams/UserResource.java | 2 +- .../openmetadata/service/resources/topics/TopicResource.java | 2 +- .../org/openmetadata/service/resources/types/TypeResource.java | 2 +- 70 files changed, 70 insertions(+), 70 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java index ed0cf186179e..6e1f364bd038 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIApplicationResource.java @@ -528,7 +528,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the AI application", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java index aba68ae768d1..4cbf1a456a2b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/AIGovernancePolicyResource.java @@ -515,7 +515,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the AI governance policy", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java index fe609e532828..ce357b08fb4e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/LLMModelResource.java @@ -495,7 +495,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the LLM model", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java index d96848d829fe..26ca0249bf47 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/McpServerResource.java @@ -549,7 +549,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the MCP server", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java index cba19c0ee01e..6980877e2559 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/ai/PromptTemplateResource.java @@ -512,7 +512,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the prompt template", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java index 0a6db828fd09..8b002e216747 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java @@ -370,7 +370,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the web analytic event", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java index 91f27554b0ed..82c90e3ce9d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java @@ -563,7 +563,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the API collection", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java index c316b9ce3fd8..aac911877929 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APIEndpointResource.java @@ -609,7 +609,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the API endpoint", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java index 9ff71de30065..4cb59f641ec2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppMarketPlaceResource.java @@ -394,7 +394,7 @@ public Response deletePrefixHardById( schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java index dd2b3858bbef..b67f5b27c3ab 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java @@ -938,7 +938,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the app", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java index 94834140b4b8..180816170730 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/automations/WorkflowResource.java @@ -547,7 +547,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the workflow", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java index fae9927ed868..c2cad05c9c3a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/bots/BotResource.java @@ -452,7 +452,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the bot", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java index ff151b75a744..ece8379534d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java @@ -547,7 +547,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the chart", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java index 2084adbc16a4..b6fd4aba0a54 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java @@ -593,7 +593,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the dashboard", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java index 9f7d76979fac..f7c48ca20976 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/data/DataContractResource.java @@ -568,7 +568,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the data contract", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java index 1f18f8ecb141..6335a77b9bb4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java @@ -733,7 +733,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the database", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java index b036cf8db38e..8efee5b4af2c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java @@ -796,7 +796,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the database schema", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java index 5c46056ee416..c1557e331983 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/StoredProcedureResource.java @@ -586,7 +586,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the stored procedure", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java index 50ca02980ce5..79c590bb2e0d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/TableResource.java @@ -767,7 +767,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the table", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java index dd37d4266ab9..a29a8753576a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/DataInsightChartResource.java @@ -472,7 +472,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the data insight chart", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java index 050c5210eebc..33f1c1b09e06 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java @@ -327,6 +327,6 @@ public Response deletePrefixHardById( schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java index c2c84d7ae3f7..06d7629daca2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datamodels/DashboardDataModelResource.java @@ -606,7 +606,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the dashboard data model", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java index a177ef28a8d0..2324efe73476 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/docstore/DocStoreResource.java @@ -470,7 +470,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the doc store", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java index 44e8651a652f..405cf0040dd6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DataProductResource.java @@ -939,7 +939,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the data product", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java index 18ecbd4c1cdd..1142c80fc1fd 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java @@ -513,7 +513,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the domain", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java index b87288209c66..99f8d6f06f0b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java @@ -942,7 +942,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the test case", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java index 58e108093284..3c4a9fa0dbe7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestDefinitionResource.java @@ -477,7 +477,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the test definition", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java index 1b173c4ff091..4e9ae67964ce 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java @@ -824,7 +824,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the test suite", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java index bcd3be2d0b3d..e48c0da1a958 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/DirectoryResource.java @@ -443,7 +443,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the directory", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java index 9e125be16aad..7761ddd02bf3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/FileResource.java @@ -435,7 +435,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the file", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java index 26f5a56efb74..8426dd2aecbb 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/SpreadsheetResource.java @@ -447,7 +447,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the spreadsheet", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java index 565c579bd1e8..602f78617b41 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/drives/WorksheetResource.java @@ -430,7 +430,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the worksheet", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java index bb3ae535cee1..d3d557dfcea3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/NotificationTemplateResource.java @@ -653,7 +653,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the notification template", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java index f4e8b20cd2f2..ce4339e4b3de 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/events/subscription/EventSubscriptionResource.java @@ -579,7 +579,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the event subscription", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java index 2a1d4e4a3d5c..12be16ac4fb7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryResource.java @@ -492,7 +492,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the glossary", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java index 51293cbcd839..4997fcbc4cea 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/glossary/GlossaryTermResource.java @@ -1064,7 +1064,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the glossary term", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java index 774caf876e84..b32c384ce0a4 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/governance/WorkflowDefinitionResource.java @@ -502,7 +502,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the workflow definition", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java index 8919c34521ca..e0d119dd244a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/kpi/KpiResource.java @@ -396,7 +396,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the KPI", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java index 796d334f4add..41deb13beef2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/learning/LearningResourceResource.java @@ -470,6 +470,6 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the learning resource", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java index e829c44577b0..14a4c4253e85 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/metrics/MetricResource.java @@ -558,7 +558,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the metric", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java index 0a049fa932a9..2357ee44d35c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/mlmodels/MlModelResource.java @@ -602,7 +602,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the ML model", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java index 52e0b9e65f90..fcff5edac6aa 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/pipelines/PipelineResource.java @@ -1236,7 +1236,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the pipeline", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java index 06294a12619d..906ae17d3200 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java @@ -523,7 +523,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the policy", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java index 9c89713fd7eb..fd93f795eca3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/query/QueryResource.java @@ -714,7 +714,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the query", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java index ac2bb19af767..d73d315eadbc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/reports/ReportResource.java @@ -246,6 +246,6 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the report", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java index 7ae2b4ae7582..0cc38a0e29de 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/searchindex/SearchIndexResource.java @@ -681,7 +681,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the search index", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java index 7cce243eaafc..67c8313986ec 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/apiservices/APIServiceResource.java @@ -540,7 +540,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the API service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java index d3009e0af1a1..f1e348b3e45b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/connections/TestConnectionDefinitionResource.java @@ -240,6 +240,6 @@ public Response deletePrefixHardById( schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java index 478c14501dc7..978b95ec70f8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/dashboard/DashboardServiceResource.java @@ -591,7 +591,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the dashboard service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java index 76c78305f66c..3e9fae1315cb 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/database/DatabaseServiceResource.java @@ -753,7 +753,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the database service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java index decee536cb1e..41b0557f5385 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/drive/DriveServiceResource.java @@ -668,7 +668,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the drive service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java index 4efab5f48c54..10c92cf58936 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java @@ -922,7 +922,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the ingestion pipeline", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java index 4356369cae12..4a9acd9b2760 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/llm/LLMServiceResource.java @@ -593,7 +593,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the LLM service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java index 0f4e78ff7ee4..3a12dc3ccf3d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mcp/McpServiceResource.java @@ -586,7 +586,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the MCP service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java index 937a17d11f3d..b2ba489fe805 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/messaging/MessagingServiceResource.java @@ -594,7 +594,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the messaging service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java index 73a92130d260..fc36dd07ccac 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/metadata/MetadataServiceResource.java @@ -634,7 +634,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the metadata service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java index b509b2d8d13b..1627cb02527d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/mlmodel/MlModelServiceResource.java @@ -607,7 +607,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the ML model service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java index a08983ed5c1c..cabdbc86994d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/pipeline/PipelineServiceResource.java @@ -607,7 +607,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the pipeline service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java index cb3e3873dfbf..7339d8b8a081 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/searchIndexes/SearchServiceResource.java @@ -593,7 +593,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the search index service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java index ff42e4ca5a92..5e8c73ac768f 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/security/SecurityServiceResource.java @@ -718,7 +718,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the security service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java index c132ea9d289d..04ffdc6c1f53 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/storage/StorageServiceResource.java @@ -591,7 +591,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the storage service", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java index 1f7de022fe1a..90b3469c3402 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java @@ -602,7 +602,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the container", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java index 0f327eb5a8bc..c41f376c60b9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/ClassificationResource.java @@ -455,7 +455,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the classification", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java index 8367da3ece02..310ef39da624 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/tags/TagResource.java @@ -534,7 +534,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the tag", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java index dd3ae086446c..059dfcb5e02d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/PersonaResource.java @@ -434,7 +434,7 @@ public Response deletePrefixHardById( @Parameter(description = "Id of the persona", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java index 3c5cf92c3cc4..a265019257dd 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/RoleResource.java @@ -492,7 +492,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the role", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java index e7f4b6196c45..294bebaf1e61 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/TeamResource.java @@ -679,7 +679,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the team", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java index 59998d47df18..30957c4e97fa 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/teams/UserResource.java @@ -1144,7 +1144,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the user", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java index 15a8c8f591f6..9b95788a7180 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/topics/TopicResource.java @@ -648,7 +648,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the topic", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java index 3ac6fd1ef027..4059e284f1d9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/types/TypeResource.java @@ -498,7 +498,7 @@ public Response deletePrefixHardById( @Context SecurityContext securityContext, @Parameter(description = "Id of the type", schema = @Schema(type = "UUID")) @PathParam("id") UUID id) { - return deletePrefixHardById(uriInfo, securityContext, id); + return super.deletePrefixHardById(uriInfo, securityContext, id); } @DELETE From 7bd398b2c3e315581dbdb1c71991676656a86083 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Fri, 10 Apr 2026 23:32:56 +0530 Subject: [PATCH 23/29] fix: chunk FeedDAO.findByEntityIds to avoid PostgreSQL 65535 parameter limit With 72k+ entity IDs from a large service hierarchy, findByEntityIds was generating a prepared statement with 72,157 parameters, exceeding PostgreSQL's 65,535 limit: PSQLException: PreparedStatement can have at most 65,535 parameters. Same fix as EntityExtensionDAO and UsageDAO: rename the raw @SqlQuery to findByEntityIdsChunk and wrap it with a default method that collects results across 50,000-ID slices. Co-Authored-By: Claude Sonnet 4.6 --- .../openmetadata/service/jdbi3/CollectionDAO.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 0f41caaca365..d1df298eb95b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -3242,7 +3242,17 @@ List> listCountThreadsByGlossaryAndTerms( List findByEntityId(@Bind("entityId") String entityId); @SqlQuery("SELECT id FROM thread_entity WHERE entityId IN ()") - List findByEntityIds(@BindList("entityIds") List entityIds); + List findByEntityIdsChunk(@BindList("entityIds") List entityIds); + + default List findByEntityIds(List entityIds) { + int chunkSize = 50_000; + List result = new ArrayList<>(); + for (int i = 0; i < entityIds.size(); i += chunkSize) { + result.addAll( + findByEntityIdsChunk(entityIds.subList(i, Math.min(i + chunkSize, entityIds.size())))); + } + return result; + } @ConnectionAwareSqlUpdate( value = From a70140bcac796bfe795b81d6e9352d2bd884f94d Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 00:38:35 +0530 Subject: [PATCH 24/29] feat: clean up Elasticsearch index during prefix hard deletion After bulk-deleting entities from the DB, call SearchRepository.deleteByEntityTypeFqnPrefix for the root entity type and every descendant type found during the scan. This removes all matching documents from each type's ES index using a single FQN prefix wildcard query per index, preventing deleted entities from continuing to appear in search results. Co-Authored-By: Claude Sonnet 4.6 --- .../service/jdbi3/PrefixDeletionService.java | 16 ++++++++++++++ .../service/search/SearchRepository.java | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java index 8783395c2372..4b205da9172c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java @@ -17,12 +17,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.openmetadata.schema.EntityInterface; import org.openmetadata.service.Entity; import org.openmetadata.service.events.lifecycle.EntityLifecycleEventDispatcher; import org.openmetadata.service.lock.HierarchicalLockManager; +import org.openmetadata.service.search.SearchRepository; import org.openmetadata.service.util.FullyQualifiedName; /** @@ -75,6 +77,7 @@ public void deletePrefixHard(EntityInterface rootEntity, String deletedBy) { deleteDependencyTables(rootFqn, fqnHashPrefix, allIds); runEntityHooks(rootEntity, deletedBy, fqnHashPrefix); deleteEntityTables(descendantsByType, rootEntity); + cleanSearchIndex(rootEntity, descendantsByType.keySet()); emitDeleteEvent(rootEntity); } finally { releaseLock(lock, rootEntity); @@ -169,6 +172,19 @@ private void deleteEntityTables( rootRepo.invalidateEntity(rootEntity); } + private void cleanSearchIndex(EntityInterface rootEntity, Set descendantTypes) { + SearchRepository searchRepo = Entity.getSearchRepository(); + if (searchRepo == null) { + return; + } + String rootFqn = rootEntity.getFullyQualifiedName(); + String rootType = rootEntity.getEntityReference().getType(); + searchRepo.deleteByEntityTypeFqnPrefix(rootType, rootFqn); + for (String entityType : descendantTypes) { + searchRepo.deleteByEntityTypeFqnPrefix(entityType, rootFqn); + } + } + private void emitDeleteEvent(EntityInterface rootEntity) { try { EntityLifecycleEventDispatcher.getInstance().onEntityDeleted(rootEntity, null); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java index b480a07eaaa3..8c4b6693d5f0 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java @@ -2178,6 +2178,28 @@ public void deleteEntityByFQNPrefix(EntityInterface entity) { } } + public void deleteByEntityTypeFqnPrefix(String entityType, String fqnPrefix) { + if (!checkIfIndexingIsSupported(entityType)) { + return; + } + if (!getSearchClient().isClientAvailable()) { + return; + } + IndexMapping indexMapping = entityIndexMap.get(entityType); + Timer.Sample searchSample = RequestLatencyContext.startSearchOperation(); + try { + searchClient.deleteEntityByFQNPrefix(indexMapping.getIndexName(clusterAlias), fqnPrefix); + } catch (Exception ie) { + LOG.error( + "Issue deleting search documents for entityType [{}] with FQN prefix [{}]", + entityType, + fqnPrefix, + ie); + } finally { + RequestLatencyContext.endSearchOperation(searchSample); + } + } + public void deleteTimeSeriesEntityById(EntityTimeSeriesInterface entity) { if (entity != null) { String entityId = entity.getId().toString(); From 16db11d1e00acfb1d71319cf84775922f1ddbb97 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 00:52:30 +0530 Subject: [PATCH 25/29] feat: run pre/post delete hooks for all entity types during prefix deletion Previously only the root entity's preDelete hook ran. This adds two new extension points to EntityRepository: - preDeleteByFqnHashPrefix(fqnHashPrefix, deletedBy): called before DB deletion for each entity type; entities are still in the DB so external systems can be cleaned up. IngestionPipelineRepository overrides this to call pipelineServiceClient.deletePipeline() for every matching Airflow pipeline and removes pipeline status time-series records. - postDeleteByFqnHashPrefix(fqnHashPrefix): called after DB deletion for post-deletion metadata work that does not require loading deleted entities. PrefixDeletionService.runEntityHooks now invokes preDeleteByFqnHashPrefix for every entity type (alongside the existing deleteTimeSeriesByFqnPrefix loop), and a new runPostEntityHooks phase fires postDeleteByFqnHashPrefix for every type after the entity tables are cleared. Co-Authored-By: Claude Sonnet 4.6 --- .../service/jdbi3/EntityRepository.java | 13 +++++++++++++ .../jdbi3/IngestionPipelineRepository.java | 18 ++++++++++++++++++ .../service/jdbi3/PrefixDeletionService.java | 16 ++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java index 04a6c47039a4..539923c9d2df 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java @@ -3447,6 +3447,19 @@ protected void postDelete(T entity, boolean hardDelete) { } } + /** + * Called once per entity type before prefix deletion removes entities from the DB. + * Override for bulk-efficient cleanup (e.g. calling an external system like Airflow). + * Entities matching the prefix are still in the DB when this runs. + */ + protected void preDeleteByFqnHashPrefix(String fqnHashPrefix, String deletedBy) {} + + /** + * Called once per entity type after prefix deletion has removed entities from the DB. + * Override for post-deletion metadata updates that do not require loading the deleted entities. + */ + protected void postDeleteByFqnHashPrefix(String fqnHashPrefix) {} + public final void deleteFromSearch(T entity, boolean hardDelete) { try (var ignored = phase("lifecycleDispatch")) { if (hardDelete) { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/IngestionPipelineRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/IngestionPipelineRepository.java index a6ad876c7eab..c9c2339f9891 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/IngestionPipelineRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/IngestionPipelineRepository.java @@ -488,6 +488,24 @@ protected void postDelete(IngestionPipeline entity, boolean hardDelete) { .delete(entity.getFullyQualifiedName(), PIPELINE_STATUS_EXTENSION); } + @Override + protected void preDeleteByFqnHashPrefix(String fqnHashPrefix, String deletedBy) { + List ids = getDao().findIdsByFqnHashPrefix(fqnHashPrefix); + for (UUID id : ids) { + try { + IngestionPipeline pipeline = find(id, Include.ALL); + if (pipelineServiceClient != null) { + pipelineServiceClient.deletePipeline(pipeline); + } + daoCollection + .entityExtensionTimeSeriesDao() + .delete(pipeline.getFullyQualifiedName(), PIPELINE_STATUS_EXTENSION); + } catch (Exception e) { + LOG.warn("Failed cleanup for ingestion pipeline {}: {}", id, e.getMessage()); + } + } + } + @Override protected EntityReference getParentReference(IngestionPipeline entity) { return entity.getService(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java index 4b205da9172c..85f8d4dedd1d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java @@ -77,6 +77,7 @@ public void deletePrefixHard(EntityInterface rootEntity, String deletedBy) { deleteDependencyTables(rootFqn, fqnHashPrefix, allIds); runEntityHooks(rootEntity, deletedBy, fqnHashPrefix); deleteEntityTables(descendantsByType, rootEntity); + runPostEntityHooks(fqnHashPrefix); cleanSearchIndex(rootEntity, descendantsByType.keySet()); emitDeleteEvent(rootEntity); } finally { @@ -151,6 +152,21 @@ private void runEntityHooks(EntityInterface rootEntity, String deletedBy, String } catch (Exception e) { LOG.debug("deleteTimeSeriesByFqnPrefix failed for type {}: {}", entityType, e.getMessage()); } + try { + Entity.getEntityRepository(entityType).preDeleteByFqnHashPrefix(fqnHashPrefix, deletedBy); + } catch (Exception e) { + LOG.debug("preDeleteByFqnHashPrefix failed for type {}: {}", entityType, e.getMessage()); + } + } + } + + private void runPostEntityHooks(String fqnHashPrefix) { + for (String entityType : Entity.getEntityList()) { + try { + Entity.getEntityRepository(entityType).postDeleteByFqnHashPrefix(fqnHashPrefix); + } catch (Exception e) { + LOG.debug("postDeleteByFqnHashPrefix failed for type {}: {}", entityType, e.getMessage()); + } } } From 682f0f61e449e036c24e23c9368d50c0b28050f7 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 01:04:44 +0530 Subject: [PATCH 26/29] feat: run pre/post hooks for all entity types during prefix deletion Adds two new extension points to EntityRepository: - preDeleteByFqnHashPrefix(fqnHashPrefix, deletedBy): runs before DB deletion; entities still exist so external systems can be cleaned up - postDeleteByFqnHashPrefix(fqnHashPrefix): runs after DB deletion for metadata-only post-deletion work Concrete overrides added: - IngestionPipelineRepository: calls Airflow pipelineServiceClient.deletePipeline() and removes pipeline status time-series records for each matching pipeline - DataContractRepository: deletes the linked TestSuite for each matching contract that has quality expectations (prevents orphaned TestSuite entities) - WorkflowDefinitionRepository: calls WorkflowHandler.deleteWorkflowDefinition() for each matching workflow definition - WorkflowRepository: calls SecretsManagerFactory.deleteSecretsFromWorkflow() for each matching workflow (prevents orphaned secrets) Also adds dao.entityExtensionTimeSeriesDao().deleteByFqnHashPrefix() to deleteDependencyTables so all entity_extension_time_series rows (pipeline status, data contract results, etc.) are bulk-cleaned in one pass. Co-Authored-By: Claude Sonnet 4.6 --- .../service/jdbi3/DataContractRepository.java | 15 +++++++++++++++ .../service/jdbi3/PrefixDeletionService.java | 1 + .../jdbi3/WorkflowDefinitionRepository.java | 17 +++++++++++++++++ .../service/jdbi3/WorkflowRepository.java | 17 +++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java index cbc426e6c81a..08018062026e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java @@ -1725,6 +1725,21 @@ public FeedRepository.TaskWorkflow getTaskWorkflow(FeedRepository.ThreadContext return super.getTaskWorkflow(threadContext); } + @Override + protected void preDeleteByFqnHashPrefix(String fqnHashPrefix, String deletedBy) { + List ids = getDao().findIdsByFqnHashPrefix(fqnHashPrefix); + for (UUID id : ids) { + try { + DataContract contract = find(id, Include.ALL); + if (!nullOrEmpty(contract.getQualityExpectations())) { + deleteTestSuite(contract); + } + } catch (Exception e) { + LOG.warn("Failed cleanup for data contract {}: {}", id, e.getMessage()); + } + } + } + @Override protected void preDelete(DataContract entity, String deletedBy) { // Inherited contracts cannot be deleted - they are virtual contracts derived from Data Product diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java index 85f8d4dedd1d..9fff9e217e4e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PrefixDeletionService.java @@ -132,6 +132,7 @@ private void deleteDependencyTables(String rootFqn, String fqnHashPrefix, List allUuids = allIds.stream().map(UUID::fromString).toList(); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDefinitionRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDefinitionRepository.java index 3c3178a2ab83..2e3f5e8cc437 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDefinitionRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowDefinitionRepository.java @@ -14,6 +14,7 @@ import org.openmetadata.schema.governance.workflows.elements.EdgeDefinition; import org.openmetadata.schema.governance.workflows.elements.WorkflowNodeDefinitionInterface; import org.openmetadata.schema.type.EntityReference; +import org.openmetadata.schema.type.Include; import org.openmetadata.schema.type.change.ChangeSource; import org.openmetadata.schema.utils.JsonUtils; import org.openmetadata.service.Entity; @@ -54,6 +55,22 @@ protected void postUpdate(WorkflowDefinition original, WorkflowDefinition update WorkflowHandler.getInstance().deploy(new Workflow(updated)); } + @Override + protected void preDeleteByFqnHashPrefix(String fqnHashPrefix, String deletedBy) { + if (!WorkflowHandler.isInitialized()) { + return; + } + List ids = getDao().findIdsByFqnHashPrefix(fqnHashPrefix); + for (UUID id : ids) { + try { + WorkflowDefinition definition = find(id, Include.ALL); + WorkflowHandler.getInstance().deleteWorkflowDefinition(definition); + } catch (Exception e) { + LOG.warn("Failed to delete workflow definition {}: {}", id, e.getMessage()); + } + } + } + @Override protected void postDelete(WorkflowDefinition entity, boolean hardDelete) { super.postDelete(entity, hardDelete); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowRepository.java index d1b3ca6173d1..d6d5728a674b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/WorkflowRepository.java @@ -4,8 +4,11 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.sqlobject.transaction.Transaction; import org.openmetadata.schema.entity.automations.Workflow; +import org.openmetadata.schema.type.Include; import org.openmetadata.schema.type.change.ChangeSource; import org.openmetadata.service.Entity; import org.openmetadata.service.resources.automations.WorkflowResource; @@ -14,6 +17,7 @@ import org.openmetadata.service.util.EntityUtil; import org.openmetadata.service.util.EntityUtil.RelationIncludes; +@Slf4j public class WorkflowRepository extends EntityRepository { private static final String PATCH_FIELDS = "status,response"; @@ -80,6 +84,19 @@ public void storeEntities(List workflows) { dao.insertMany(dao.getTableName(), dao.getNameHashColumn(), fqns, jsons); } + @Override + protected void preDeleteByFqnHashPrefix(String fqnHashPrefix, String deletedBy) { + List ids = getDao().findIdsByFqnHashPrefix(fqnHashPrefix); + for (UUID id : ids) { + try { + Workflow workflow = find(id, Include.ALL); + SecretsManagerFactory.getSecretsManager().deleteSecretsFromWorkflow(workflow); + } catch (Exception e) { + LOG.warn("Failed to delete secrets for workflow {}: {}", id, e.getMessage()); + } + } + } + /** Remove the secrets from the secret manager */ @Override protected void postDelete(Workflow workflow, boolean hardDelete) { From 922742795588cb1f820f53c2778d63bcc8cb0274 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 01:18:22 +0530 Subject: [PATCH 27/29] fix: use information_schema conditional for ADD COLUMN in MySQL migration MySQL 8.x does not support ADD COLUMN IF NOT EXISTS (that is a MariaDB extension). Replace with the SET @sql / PREPARE / EXECUTE / DEALLOCATE pattern already used elsewhere in this migration file to conditionally add fromFQNHash and toFQNHash columns to entity_relationship. CREATE INDEX IF NOT EXISTS is supported by MySQL 8.0+ so those lines are left unchanged. Co-Authored-By: Claude Sonnet 4.6 --- .../native/1.13.0/mysql/schemaChanges.sql | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql index 76066e198c94..f5deb62623cf 100644 --- a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql @@ -133,9 +133,28 @@ WHERE ue.name = 'mcpapplicationbot' -- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. -- This allows deleting all relationships for an entire entity subtree in a single indexed query -- instead of walking the tree entity-by-entity. -ALTER TABLE entity_relationship - ADD COLUMN IF NOT EXISTS fromFQNHash VARCHAR(768) DEFAULT NULL, - ADD COLUMN IF NOT EXISTS toFQNHash VARCHAR(768) DEFAULT NULL; +-- MySQL does not support ADD COLUMN IF NOT EXISTS, so we use information_schema checks. +SET @add_from_fqn_hash := ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE entity_relationship ADD COLUMN fromFQNHash VARCHAR(768) DEFAULT NULL', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'entity_relationship' AND column_name = 'fromFQNHash' +); +PREPARE add_from_fqn_hash_stmt FROM @add_from_fqn_hash; +EXECUTE add_from_fqn_hash_stmt; +DEALLOCATE PREPARE add_from_fqn_hash_stmt; + +SET @add_to_fqn_hash := ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE entity_relationship ADD COLUMN toFQNHash VARCHAR(768) DEFAULT NULL', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'entity_relationship' AND column_name = 'toFQNHash' +); +PREPARE add_to_fqn_hash_stmt FROM @add_to_fqn_hash; +EXECUTE add_to_fqn_hash_stmt; +DEALLOCATE PREPARE add_to_fqn_hash_stmt; CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); From 9380afa4ca8b4acbe00b1c89b8e0cf8dafa0ec1f Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 22:38:25 +0530 Subject: [PATCH 28/29] fix: drop IF NOT EXISTS from CREATE INDEX in MySQL migration The internal migration runner handles duplicate index checks. Plain CREATE INDEX is simpler and works across all MySQL 8.x versions. Co-Authored-By: Claude Sonnet 4.6 --- .../sql/migrations/native/1.13.0/mysql/schemaChanges.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql index f5deb62623cf..0d7c85772e8a 100644 --- a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql @@ -156,5 +156,5 @@ PREPARE add_to_fqn_hash_stmt FROM @add_to_fqn_hash; EXECUTE add_to_fqn_hash_stmt; DEALLOCATE PREPARE add_to_fqn_hash_stmt; -CREATE INDEX IF NOT EXISTS idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); -CREATE INDEX IF NOT EXISTS idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); +CREATE INDEX idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); +CREATE INDEX idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768)); From 9c3e4d6b2515f4208fbc5118626239560ce57623 Mon Sep 17 00:00:00 2001 From: mohitdeuex Date: Sat, 11 Apr 2026 22:40:33 +0530 Subject: [PATCH 29/29] fix: simplify MySQL migration to plain ALTER TABLE and CREATE INDEX Remove the information_schema conditional workarounds. The internal migration runner handles idempotency, so plain DDL is sufficient. Co-Authored-By: Claude Sonnet 4.6 --- .../native/1.13.0/mysql/schemaChanges.sql | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql index 0d7c85772e8a..1d7d13817319 100644 --- a/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.13.0/mysql/schemaChanges.sql @@ -133,28 +133,9 @@ WHERE ue.name = 'mcpapplicationbot' -- Add FQN hash columns to entity_relationship to enable fast prefix-based bulk deletion. -- This allows deleting all relationships for an entire entity subtree in a single indexed query -- instead of walking the tree entity-by-entity. --- MySQL does not support ADD COLUMN IF NOT EXISTS, so we use information_schema checks. -SET @add_from_fqn_hash := ( - SELECT IF(COUNT(*) = 0, - 'ALTER TABLE entity_relationship ADD COLUMN fromFQNHash VARCHAR(768) DEFAULT NULL', - 'SELECT 1') - FROM information_schema.columns - WHERE table_schema = DATABASE() AND table_name = 'entity_relationship' AND column_name = 'fromFQNHash' -); -PREPARE add_from_fqn_hash_stmt FROM @add_from_fqn_hash; -EXECUTE add_from_fqn_hash_stmt; -DEALLOCATE PREPARE add_from_fqn_hash_stmt; - -SET @add_to_fqn_hash := ( - SELECT IF(COUNT(*) = 0, - 'ALTER TABLE entity_relationship ADD COLUMN toFQNHash VARCHAR(768) DEFAULT NULL', - 'SELECT 1') - FROM information_schema.columns - WHERE table_schema = DATABASE() AND table_name = 'entity_relationship' AND column_name = 'toFQNHash' -); -PREPARE add_to_fqn_hash_stmt FROM @add_to_fqn_hash; -EXECUTE add_to_fqn_hash_stmt; -DEALLOCATE PREPARE add_to_fqn_hash_stmt; +ALTER TABLE entity_relationship + ADD COLUMN fromFQNHash VARCHAR(768) DEFAULT NULL, + ADD COLUMN toFQNHash VARCHAR(768) DEFAULT NULL; CREATE INDEX idx_er_from_fqn_hash ON entity_relationship (fromFQNHash(768)); CREATE INDEX idx_er_to_fqn_hash ON entity_relationship (toFQNHash(768));