Skip to content

Commit dca1e9b

Browse files
authored
[feat] add findEntitiesV2 to support logical expression filter (#568)
1 parent 1b4e026 commit dca1e9b

2 files changed

Lines changed: 96 additions & 13 deletions

File tree

dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/EbeanLocalRelationshipQueryDAO.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,15 @@ public void setSchemaConfig(EbeanLocalDAO.SchemaConfig schemaConfig) {
110110
@Nonnull
111111
public <SNAPSHOT extends RecordTemplate> List<SNAPSHOT> findEntities(@Nonnull Class<SNAPSHOT> snapshotClass,
112112
@Nonnull LocalRelationshipFilter filter, int offset, int count) throws OperationNotSupportedException {
113+
return findEntitiesCore(snapshotClass, filter, offset, count, false);
114+
}
115+
116+
private <SNAPSHOT extends RecordTemplate> List<SNAPSHOT> findEntitiesCore(@Nonnull Class<SNAPSHOT> snapshotClass,
117+
@Nonnull LocalRelationshipFilter filter, int offset, int count, boolean logicalExpressionFilterEnabled) throws OperationNotSupportedException {
113118
if (_schemaConfig == EbeanLocalDAO.SchemaConfig.OLD_SCHEMA_ONLY) {
114119
throw new OperationNotSupportedException("findEntities is not supported in OLD_SCHEMA_MODE");
115120
}
116-
validateEntityFilter(filter, snapshotClass);
121+
validateEntityFilter(filter, snapshotClass, logicalExpressionFilterEnabled);
117122

118123
final String tableName = SQLSchemaUtils.getTableName(ModelUtils.getUrnTypeFromSnapshot(snapshotClass));
119124
final StringBuilder sqlBuilder = new StringBuilder();
@@ -129,6 +134,25 @@ public <SNAPSHOT extends RecordTemplate> List<SNAPSHOT> findEntities(@Nonnull Cl
129134
.collect(Collectors.toList());
130135
}
131136

137+
/**
138+
* Finds a list of entities of a specific type based on the given filter on the entity.
139+
* Similar to {@link #findEntities(Class, LocalRelationshipFilter, int, int)},
140+
* but this method uses the LogicalExpressionLocalRelationshipCriterion in LocalRelationshipFilter.
141+
* The SNAPSHOT class must be defined within com.linkedin.metadata.snapshot package in metadata-models.
142+
* This method is not supported in OLD_SCHEMA_ONLY mode.
143+
* @param snapshotClass the snapshot class to query.
144+
* @param filter the filter to apply when querying. Uses `logicalExpressionCriteria` instead of `criteria`.
145+
* @param offset the offset the query should start at. Ignored if set to a negative value.
146+
* @param count the maximum number of entities to return. Ignored if set to a non-positive value.
147+
* @return A list of entity records of class SNAPSHOT.
148+
* @throws OperationNotSupportedException when called in OLD_SCHEMA_ONLY mode. This exception must be explicitly handled by the caller.
149+
*/
150+
@Nonnull
151+
public <SNAPSHOT extends RecordTemplate> List<SNAPSHOT> findEntitiesV2(@Nonnull Class<SNAPSHOT> snapshotClass,
152+
@Nonnull LocalRelationshipFilter filter, int offset, int count) throws OperationNotSupportedException {
153+
return findEntitiesCore(snapshotClass, filter, offset, count, true);
154+
}
155+
132156
/**
133157
* Finds a list of entities of a specific type based on the given source, destination, and relationship filters.
134158
* Every SNAPSHOT class must be defined within com.linkedin.metadata.snapshot package in metadata-models.
@@ -449,18 +473,15 @@ private String getMgEntityTableName(@Nullable String entityType) {
449473
return SQLSchemaUtils.getTableName(entityType);
450474
}
451475

452-
/**
453-
* Validate:
454-
* 1. The entity filter only contains supported conditions.
455-
* 2. if the entity class is null, then the filter should be empty.
456-
* If any of above is violated, throw an IllegalArgumentException.
457-
*/
458476
private <ENTITY extends RecordTemplate> void validateEntityFilter(@Nonnull LocalRelationshipFilter filter, @Nullable Class<ENTITY> entityClass) {
459-
if (entityClass == null && filter.hasCriteria() && filter.getCriteria().size() > 0) {
460-
throw new IllegalArgumentException("Entity class is null but filter is not empty.");
461-
}
477+
validateEntityFilter(filter, entityClass, false);
478+
}
462479

463-
validateFilterCriteria(filter, false);
480+
private <ENTITY extends RecordTemplate> void validateEntityFilter(@Nonnull LocalRelationshipFilter filter, @Nullable Class<ENTITY> entityClass,
481+
boolean logicalExpressionFilterEnabled) {
482+
validateEntityTypeAndFilter(filter,
483+
entityClass != null ? ModelUtils.getUrnTypeFromSnapshot(entityClass) : null,
484+
logicalExpressionFilterEnabled);
464485
}
465486

466487
/**
@@ -498,10 +519,10 @@ private static void validateLogicalExpressionFilter(@Nonnull LocalRelationshipFi
498519

499520
if (logicalExpressionFilterEnabled && filter.hasCriteria()) {
500521
throw new IllegalArgumentException(
501-
"Please do not use the 'criteria' field and use the 'logicalExpressionCriteria' field instead for findRelationshipsV4 API.");
522+
"Please do not use the 'criteria' field and use the 'logicalExpressionCriteria' field instead for this API.");
502523
} else if (!logicalExpressionFilterEnabled && filter.hasLogicalExpressionCriteria()) {
503524
throw new IllegalArgumentException(
504-
"Please do not use the 'logicalExpressionCriteria' field and use the 'criteria' field instead for findRelationshipsV2/V3 API.");
525+
"Please do not use the 'logicalExpressionCriteria' field and use the 'criteria' field instead for this API.");
505526
}
506527
}
507528

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,68 @@ public void testFindOneEntityTwoAspects() throws URISyntaxException, OperationNo
175175
assertEquals(fooSnapshotList.get(0).getAspects(), expected);
176176
}
177177

178+
@Test
179+
public void testFindEntitiesV2WithV1Filter() throws URISyntaxException, OperationNotSupportedException {
180+
// Ingest data
181+
_fooUrnEBeanLocalAccess.add(new FooUrn(1), new AspectFoo().setValue("foo"), AspectFoo.class, new AuditStamp(), null, false);
182+
183+
// Prepare filter
184+
LocalRelationshipCriterion filterCriterion = EBeanDAOUtils.buildRelationshipFieldCriterion(LocalRelationshipValue.create("foo"),
185+
Condition.EQUAL,
186+
new AspectField().setAspect(AspectFoo.class.getCanonicalName()).setPath("/value"));
187+
188+
LocalRelationshipFilter filter = new LocalRelationshipFilter().setCriteria(new LocalRelationshipCriterionArray(filterCriterion));
189+
190+
assertThrows(IllegalArgumentException.class, () -> _localRelationshipQueryDAO.findEntitiesV2(FooSnapshot.class, filter, 0, 10));
191+
}
192+
193+
@Test
194+
public void testFindOneEntityV2() throws URISyntaxException, OperationNotSupportedException {
195+
// Ingest data
196+
_fooUrnEBeanLocalAccess.add(new FooUrn(1), new AspectFoo().setValue("foo"), AspectFoo.class, new AuditStamp(), null, false);
197+
198+
// Prepare filter
199+
LocalRelationshipCriterion filterCriterion = EBeanDAOUtils.buildRelationshipFieldCriterion(LocalRelationshipValue.create("foo"),
200+
Condition.EQUAL,
201+
new AspectField().setAspect(AspectFoo.class.getCanonicalName()).setPath("/value"));
202+
203+
LocalRelationshipFilter filter = new LocalRelationshipFilter().setLogicalExpressionCriteria(
204+
wrapCriterionAsLogicalExpression(filterCriterion));
205+
List<FooSnapshot> fooSnapshotList = _localRelationshipQueryDAO.findEntitiesV2(FooSnapshot.class, filter, 0, 10);
206+
207+
assertEquals(fooSnapshotList.size(), 1);
208+
assertEquals(fooSnapshotList.get(0).getAspects().size(), 1);
209+
assertEquals(fooSnapshotList.get(0).getAspects().get(0).getAspectFoo(), new AspectFoo().setValue("foo"));
210+
}
211+
212+
@Test
213+
public void testFindOneEntityTwoAspectsV2() throws URISyntaxException, OperationNotSupportedException {
214+
// Ingest data
215+
_fooUrnEBeanLocalAccess.add(new FooUrn(1), new AspectFoo().setValue("foo"), AspectFoo.class, new AuditStamp(), null, false);
216+
_fooUrnEBeanLocalAccess.add(new FooUrn(1), new AspectBar().setValue("bar"), AspectBar.class, new AuditStamp(), null, false);
217+
218+
// Prepare filter
219+
LocalRelationshipCriterion filterCriterion = EBeanDAOUtils.buildRelationshipFieldCriterion(LocalRelationshipValue.create("foo"),
220+
Condition.EQUAL,
221+
new AspectField().setAspect(AspectFoo.class.getCanonicalName()).setPath("/value"));
222+
223+
LocalRelationshipFilter filter = new LocalRelationshipFilter().setLogicalExpressionCriteria(
224+
wrapCriterionAsLogicalExpression(filterCriterion));
225+
226+
List<FooSnapshot> fooSnapshotList = _localRelationshipQueryDAO.findEntitiesV2(FooSnapshot.class, filter, 0, 10);
227+
228+
assertEquals(fooSnapshotList.size(), 1);
229+
assertEquals(fooSnapshotList.get(0).getAspects().size(), 2);
230+
EntityAspectUnion fooAspectUnion = new EntityAspectUnion();
231+
fooAspectUnion.setAspectFoo(new AspectFoo().setValue("foo"));
232+
EntityAspectUnion barAspectUnion = new EntityAspectUnion();
233+
barAspectUnion.setAspectBar(new AspectBar().setValue("bar"));
234+
235+
EntityAspectUnionArray expected = new EntityAspectUnionArray(fooAspectUnion, barAspectUnion);
236+
237+
assertEquals(fooSnapshotList.get(0).getAspects(), expected);
238+
}
239+
178240
@DataProvider(name = "schemaConfig")
179241
public static Object[][] schemaConfig() {
180242
return new Object[][] {

0 commit comments

Comments
 (0)