Skip to content

Commit 2de35c0

Browse files
authored
(feat) Add deleteMany Dao method (#496)
1 parent 707986e commit 2de35c0

File tree

3 files changed

+136
-6
lines changed

3 files changed

+136
-6
lines changed

.github/workflows/build-and-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Upload artifact
3434
if: always()
35-
uses: actions/upload-artifact@v3
35+
uses: actions/upload-artifact@v4
3636
with:
3737
name: test-reports
3838
path: /home/runner/work/datahub-gma/datahub-gma/dao-impl/ebean-dao/build/reports/tests/test/**

dao-api/src/main/java/com/linkedin/metadata/dao/BaseLocalDAO.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,61 @@ public <ASPECT extends RecordTemplate> ASPECT add(@Nonnull URN urn, AspectUpdate
811811
: unwrapAddResult(urn, result, auditStamp, trackingContext));
812812
}
813813

814+
/**
815+
* Deletes the latest version of aspects for an entity. Deletion is an atomic operation; either all aspects are deleted or none are.
816+
*
817+
* <p>The new aspect will have an automatically assigned version number, which is guaranteed to be positive and
818+
* monotonically increasing. Older versions of aspect will be purged automatically based on the retention setting.
819+
*
820+
* <p>Note that we do not support Post-update hooks while soft deleting an aspect
821+
*
822+
* @param urn urn the URN for the entity the aspects are attached to
823+
* @param aspectClasses Aspect Classes of the aspects being deleted, must be supported aspect types in {@code ASPECT_UNION}
824+
* Because Aspect Classes must be unique for a given Entity, we use a set to avoid duplicates.
825+
* @param auditStamp the audit stamp of this action
826+
* @param maxTransactionRetry maximum number of transaction retries before throwing an exception
827+
*/
828+
public void deleteMany(@Nonnull URN urn,
829+
@Nonnull Set<Class<? extends RecordTemplate>> aspectClasses,
830+
@Nonnull AuditStamp auditStamp,
831+
int maxTransactionRetry) {
832+
deleteMany(urn, aspectClasses, auditStamp, maxTransactionRetry, null);
833+
}
834+
835+
/**
836+
* Similar to {@link #deleteMany(Urn, Set, AuditStamp, int)} but uses the default maximum transaction retry.
837+
*/
838+
@Nonnull
839+
public void deleteMany(@Nonnull URN urn, @Nonnull Set<Class<? extends RecordTemplate>> aspectClasses,
840+
@Nonnull AuditStamp auditStamp) {
841+
deleteMany(urn, aspectClasses, auditStamp, DEFAULT_MAX_TRANSACTION_RETRY);
842+
}
843+
844+
/**
845+
* Same as above {@link #deleteMany(Urn, Set, AuditStamp)} but with tracking context.
846+
*/
847+
@Nonnull
848+
public void deleteMany(@Nonnull URN urn, @Nonnull Set<Class<? extends RecordTemplate>> aspectClasses,
849+
@Nonnull AuditStamp auditStamp, @Nullable IngestionTrackingContext trackingContext) {
850+
deleteMany(urn, aspectClasses, auditStamp, DEFAULT_MAX_TRANSACTION_RETRY, trackingContext);
851+
}
852+
853+
/**
854+
* Same as {@link #deleteMany(Urn, Set, AuditStamp, int)} but with tracking context.
855+
*/
856+
public void deleteMany(@Nonnull URN urn,
857+
@Nonnull Set<Class<? extends RecordTemplate>> aspectClasses,
858+
@Nonnull AuditStamp auditStamp,
859+
int maxTransactionRetry,
860+
@Nullable IngestionTrackingContext trackingContext) {
861+
862+
// entire delete operation should be atomic
863+
runInTransactionWithRetry(() -> {
864+
aspectClasses.forEach(x -> delete(urn, x, auditStamp, maxTransactionRetry, trackingContext));
865+
return null;
866+
}, maxTransactionRetry);
867+
}
868+
814869
/**
815870
* Deletes the latest version of aspect for an entity.
816871
*

dao-impl/ebean-dao/src/test/java/com/linkedin/metadata/dao/EbeanLocalDAOTest.java

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,13 +2540,16 @@ public void testUndeleteSoftDeletedAspect() {
25402540
verifyNoMoreInteractions(_mockProducer);
25412541
}
25422542

2543-
@Test
2544-
public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxException {
2545-
EbeanLocalDAO<EntityAspectUnion, FooUrn> fooDao = createDao(FooUrn.class);
2543+
// common setup logic to the next two tests for relationship removal
2544+
private void setupAspectsAndRelationships(
2545+
FooUrn fooUrn,
2546+
EbeanLocalDAO<EntityAspectUnion, FooUrn> fooDao) throws URISyntaxException {
2547+
// necessary flag to prevent removal of existing same-type relationships in "another aspect"
2548+
fooDao.setUseAspectColumnForRelationshipRemoval(true);
2549+
25462550
EbeanLocalDAO<EntityAspectUnion, BarUrn> barDao = createDao(BarUrn.class);
25472551

25482552
// add an aspect (AspectFooBar) which includes BelongsTo relationships and ReportsTo relationships
2549-
FooUrn fooUrn = makeFooUrn(1);
25502553
BarUrn barUrn1 = BarUrn.createFromString("urn:li:bar:1");
25512554
BelongsToV2 belongsTo1 = new BelongsToV2().setDestination(BelongsToV2.Destination.create(barUrn1.toString()));
25522555
BarUrn barUrn2 = BarUrn.createFromString("urn:li:bar:2");
@@ -2565,6 +2568,23 @@ public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxEx
25652568
barDao.add(barUrn2, new AspectFoo().setValue("2"), auditStamp);
25662569
barDao.add(barUrn3, new AspectFoo().setValue("3"), auditStamp);
25672570

2571+
// add an aspect (AspectFooBaz) which includes BelongsTo relationships
2572+
BarUrn barUrn4 = BarUrn.createFromString("urn:li:bar:4");
2573+
BelongsToV2 belongsTo4 = new BelongsToV2().setDestination(BelongsToV2.Destination.create(barUrn4.toString()));
2574+
BelongsToV2Array belongsToArray2 = new BelongsToV2Array(belongsTo4);
2575+
AspectFooBaz aspectFooBaz = new AspectFooBaz().setBars(new BarUrnArray(barUrn4)).setBelongsTos(belongsToArray2);
2576+
2577+
fooDao.add(fooUrn, aspectFooBaz, auditStamp);
2578+
barDao.add(barUrn4, new AspectFoo().setValue("4"), auditStamp);
2579+
}
2580+
2581+
@Test
2582+
public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxException {
2583+
FooUrn fooUrn = makeFooUrn(1);
2584+
EbeanLocalDAO<EntityAspectUnion, FooUrn> fooDao = createDao(FooUrn.class);
2585+
2586+
setupAspectsAndRelationships(fooUrn, fooDao);
2587+
25682588
// Verify local relationships and entities are added.
25692589
EbeanLocalRelationshipQueryDAO ebeanLocalRelationshipQueryDAO = new EbeanLocalRelationshipQueryDAO(_server);
25702590
ebeanLocalRelationshipQueryDAO.setSchemaConfig(_schemaConfig);
@@ -2573,7 +2593,7 @@ public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxEx
25732593
ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
25742594
EMPTY_FILTER, BelongsToV2.class, OUTGOING_FILTER, 0, 10);
25752595

2576-
assertEquals(resultBelongsTos.size(), 3);
2596+
assertEquals(resultBelongsTos.size(), 4);
25772597

25782598
List<ReportsTo> resultReportsTos =
25792599
ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
@@ -2593,6 +2613,57 @@ public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxEx
25932613
resultBelongsTos = ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
25942614
EMPTY_FILTER, BelongsToV2.class, OUTGOING_FILTER, 0, 10);
25952615

2616+
// since we only deleted 1 of the 2 Aspects with BelongsTo relationships, we should still have 1 BelongsTo relationship
2617+
assertEquals(resultBelongsTos.size(), 1);
2618+
2619+
// check that the reportsTo relationship was soft deleted
2620+
resultReportsTos =
2621+
ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
2622+
EMPTY_FILTER, ReportsTo.class, OUTGOING_FILTER, 0, 10);
2623+
2624+
assertEquals(resultReportsTos.size(), 0);
2625+
2626+
// check that the AspectFooBar aspect was soft deleted
2627+
Optional<AspectWithExtraInfo<AspectFooBar>> optionalAspect = fooDao.getWithExtraInfo(AspectFooBar.class, fooUrn, 0L);
2628+
assertFalse(optionalAspect.isPresent());
2629+
}
2630+
2631+
// basically a copy of the above test but makes use of the deleteMany() call
2632+
@Test
2633+
public void testDeleteManyWithRelationshipRemoval() throws URISyntaxException {
2634+
FooUrn fooUrn = makeFooUrn(1);
2635+
EbeanLocalDAO<EntityAspectUnion, FooUrn> fooDao = createDao(FooUrn.class);
2636+
2637+
setupAspectsAndRelationships(fooUrn, fooDao);
2638+
2639+
// Verify local relationships and entities are added.
2640+
EbeanLocalRelationshipQueryDAO ebeanLocalRelationshipQueryDAO = new EbeanLocalRelationshipQueryDAO(_server);
2641+
ebeanLocalRelationshipQueryDAO.setSchemaConfig(_schemaConfig);
2642+
2643+
List<BelongsToV2> resultBelongsTos =
2644+
ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
2645+
EMPTY_FILTER, BelongsToV2.class, OUTGOING_FILTER, 0, 10);
2646+
2647+
assertEquals(resultBelongsTos.size(), 4);
2648+
2649+
List<ReportsTo> resultReportsTos =
2650+
ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
2651+
EMPTY_FILTER, ReportsTo.class, OUTGOING_FILTER, 0, 10);
2652+
2653+
assertEquals(resultReportsTos.size(), 1);
2654+
2655+
AspectKey<FooUrn, AspectFooBar> key = new AspectKey<>(AspectFooBar.class, fooUrn, 0L);
2656+
List<EbeanMetadataAspect> aspects = fooDao.batchGetHelper(Collections.singletonList(key), 1, 0);
2657+
2658+
assertEquals(aspects.size(), 1);
2659+
2660+
// soft delete the AspectFooBar and AspectFooBaz aspects
2661+
fooDao.deleteMany(fooUrn, new HashSet<>(Arrays.asList(AspectFooBar.class, AspectFooBaz.class)), _dummyAuditStamp);
2662+
2663+
// check that the belongsTo relationships 1, 2, 3, and 4 were soft deleted
2664+
resultBelongsTos = ebeanLocalRelationshipQueryDAO.findRelationships(FooSnapshot.class, EMPTY_FILTER, BarSnapshot.class,
2665+
EMPTY_FILTER, BelongsToV2.class, OUTGOING_FILTER, 0, 10);
2666+
25962667
assertEquals(resultBelongsTos.size(), 0);
25972668

25982669
// check that the reportsTo relationship was soft deleted
@@ -2605,6 +2676,10 @@ public void testRemoveRelationshipsDuringAspectSoftDeletion() throws URISyntaxEx
26052676
// check that the AspectFooBar aspect was soft deleted
26062677
Optional<AspectWithExtraInfo<AspectFooBar>> optionalAspect = fooDao.getWithExtraInfo(AspectFooBar.class, fooUrn, 0L);
26072678
assertFalse(optionalAspect.isPresent());
2679+
2680+
// check that the AspectFooBaz aspect was soft deleted
2681+
Optional<AspectWithExtraInfo<AspectFooBaz>> optionalAspect2 = fooDao.getWithExtraInfo(AspectFooBaz.class, fooUrn, 0L);
2682+
assertFalse(optionalAspect2.isPresent());
26082683
}
26092684

26102685
@Test

0 commit comments

Comments
 (0)