From 7b421454b3cfedd0823d0f746bcbb5d9bc9c27a3 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Sat, 4 Apr 2026 21:06:19 +0530 Subject: [PATCH] fix: Guard postDelete() with hardDelete check in IngestionPipeline, DataContract, App repositories Wraps destructive time series cleanup and external service calls in if (hardDelete) blocks to prevent data loss on soft delete. Previously, soft-deleting these entities permanently destroyed: - IngestionPipeline: pipeline status time series + deployed Airflow pipeline - DataContract: contract result time series + associated test suite - App: run status history Since restoreEntity() only flips the deleted flag, a soft-delete/restore cycle caused irrecoverable data loss. Fixes #27040 --- .../service/jdbi3/AppRepository.java | 14 ++++++----- .../service/jdbi3/DataContractRepository.java | 14 ++++++----- .../jdbi3/IngestionPipelineRepository.java | 24 ++++++++++--------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java index 3c965f107494..52583c194aa1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java @@ -248,12 +248,14 @@ public void storeRelationships(App entity) { @Override protected void postDelete(App entity, boolean hardDelete) { super.postDelete(entity, hardDelete); - // Delete the status stored in the app extension - // Note that we don't want to delete the LIMITS, since we want to keep them - // between different app installations - daoCollection - .appExtensionTimeSeriesDao() - .delete(entity.getId().toString(), AppExtension.ExtensionType.STATUS.toString()); + if (hardDelete) { + // Delete the status stored in the app extension + // Note that we don't want to delete the LIMITS, since we want to keep them + // between different app installations + daoCollection + .appExtensionTimeSeriesDao() + .delete(entity.getId().toString(), AppExtension.ExtensionType.STATUS.toString()); + } } public final List listAll() { 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..47dc9941bd17 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 @@ -240,13 +240,15 @@ protected void postUpdate(DataContract original, DataContract updated) { @Override protected void postDelete(DataContract dataContract, boolean hardDelete) { super.postDelete(dataContract, hardDelete); - if (!nullOrEmpty(dataContract.getQualityExpectations())) { - deleteTestSuite(dataContract); + if (hardDelete) { + if (!nullOrEmpty(dataContract.getQualityExpectations())) { + deleteTestSuite(dataContract); + } + // Clean status + daoCollection + .entityExtensionTimeSeriesDao() + .delete(dataContract.getFullyQualifiedName(), RESULT_EXTENSION); } - // Clean status - daoCollection - .entityExtensionTimeSeriesDao() - .delete(dataContract.getFullyQualifiedName(), RESULT_EXTENSION); } private void postCreateOrUpdate(DataContract dataContract) { 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..317c1ae2f035 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 @@ -474,18 +474,20 @@ public EntityRepository.EntityUpdater getUpdater( @Override protected void postDelete(IngestionPipeline entity, boolean hardDelete) { super.postDelete(entity, hardDelete); - // Delete deployed pipeline in the Pipeline Service Client - if (pipelineServiceClient != null) { - pipelineServiceClient.deletePipeline(entity); - } else { - LOG.debug( - "Skipping pipeline service delete for '{}' because pipeline service client is not configured.", - entity.getFullyQualifiedName()); + if (hardDelete) { + // Delete deployed pipeline in the Pipeline Service Client + if (pipelineServiceClient != null) { + pipelineServiceClient.deletePipeline(entity); + } else { + LOG.debug( + "Skipping pipeline service delete for '{}' because pipeline service client is not configured.", + entity.getFullyQualifiedName()); + } + // Clean pipeline status + daoCollection + .entityExtensionTimeSeriesDao() + .delete(entity.getFullyQualifiedName(), PIPELINE_STATUS_EXTENSION); } - // Clean pipeline status - daoCollection - .entityExtensionTimeSeriesDao() - .delete(entity.getFullyQualifiedName(), PIPELINE_STATUS_EXTENSION); } @Override