Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(structuredproperties): aggregration fix & docs #10780

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions docs/api/tutorials/structured-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This guide will show you how to execute the following actions with structured pr
- Add structured properties to a dataset
- Patch structured properties (add / remove / update a single property)
- Update structured property with breaking schema changes
- Search using structured properties
- Search & aggregations using structured properties

## Prerequisites

Expand Down Expand Up @@ -1107,7 +1107,7 @@ schema changes will remove previously written data.
Breaking schema changes are implemented by setting a version string within the Structured Property definition. This
version must be in the following format: `yyyyMMddhhmmss`, i.e. `20240614080000`

:::IMPORTANT NOTES
:::note IMPORTANT NOTES
Old values will not be retrieve-able after the new Structured Property definition is applied.

The old values will be subject to deletion asynchronously (future work).
Expand Down Expand Up @@ -1234,7 +1234,7 @@ Example Response:
</TabItem>
</Tabs>

## Structured Properties & Search
## Structured Properties - Search & Aggregation

Currently Structured Properties can be used to filter search results. This currently excludes fulltext search.

Expand Down Expand Up @@ -1525,5 +1525,73 @@ Example Response:
}
```

</TabItem>
</Tabs>

### Structured Property Aggregations

Structured properties can also be used in GraphQL's aggregation queries using the same naming convention outlined above
for search filter field names. There are currently no aggregation endpoints for OpenAPI.

<Tabs>
<TabItem value="GraphQL" label="GraphQL" default>

Aggregation Query:

```graphql
query {
aggregateAcrossEntities(
input: {
types: [],
facets: [
"structuredProperties.io.acryl.privacy.retentionTime02",
"structuredProperties.io.acryl.privacy.retentionTime"],
query: "*",
orFilters: [],
searchFlags: {maxAggValues: 100}
}) {
facets {
field
aggregations {
value
count
}
}
}
}
```

Example Response:

```json
{
"data": {
"aggregateAcrossEntities": {
"facets": [
{
"field": "structuredProperties.io.acryl.privacy.retentionTime02",
"aggregations": [
{
"value": "bar2",
"count": 1
}
]
},
{
"field": "structuredProperties.io.acryl.privacy.retentionTime",
"aggregations": [
{
"value": "60.0",
"count": 1
}
]
}
]
}
},
"extensions": {}
}
```

</TabItem>
</Tabs>
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,34 @@ public static String getValueTypeId(@Nullable final Urn valueType) {
}
}

/**
* Given a structured property input field or facet name, return a valid structured property facet
* name
*
* @param fieldOrFacetName input name
* @param aspectRetriever aspect retriever
* @return guranteed facet name
*/
public static Optional<String> toStructuredPropertyFacetName(
@Nonnull String fieldOrFacetName, @Nullable AspectRetriever aspectRetriever) {
return lookupDefinitionFromFilterOrFacetName(fieldOrFacetName, aspectRetriever)
.map(
urnDefinition -> {
switch (getLogicalValueType(urnDefinition.getSecond())) {
case DATE:
case NUMBER:
return STRUCTURED_PROPERTY_MAPPING_FIELD_PREFIX
+ StructuredPropertyUtils.toElasticsearchFieldName(
urnDefinition.getFirst(), urnDefinition.getSecond());
default:
return STRUCTURED_PROPERTY_MAPPING_FIELD_PREFIX
+ StructuredPropertyUtils.toElasticsearchFieldName(
urnDefinition.getFirst(), urnDefinition.getSecond())
+ ".keyword";
}
});
}

/**
* Lookup structured property definition given the name used for the field in APIs such as a
* search filter or aggregation query facet name.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.linkedin.metadata.search.elasticsearch.query.request;

import static com.linkedin.metadata.Constants.*;
import static com.linkedin.metadata.models.StructuredPropertyUtils.toStructuredPropertyFacetName;
import static com.linkedin.metadata.search.utils.ESUtils.toParentField;
import static com.linkedin.metadata.utils.SearchUtil.*;

import com.linkedin.data.template.LongMap;
import com.linkedin.metadata.aspect.AspectRetriever;
import com.linkedin.metadata.config.search.SearchConfiguration;
import com.linkedin.metadata.models.EntitySpec;
import com.linkedin.metadata.models.StructuredPropertyUtils;
import com.linkedin.metadata.models.annotation.SearchableAnnotation;
import com.linkedin.metadata.query.filter.ConjunctiveCriterion;
import com.linkedin.metadata.query.filter.ConjunctiveCriterionArray;
Expand Down Expand Up @@ -136,15 +136,7 @@ private AggregationBuilder facetToAggregationBuilder(
opContext.getSearchContext().getSearchFlags().getMaxAggValues(),
configs.getMaxTermBucketSize());
for (int i = facets.size() - 1; i >= 0; i--) {
String facet =
StructuredPropertyUtils.lookupDefinitionFromFilterOrFacetName(
facets.get(i), opContext.getAspectRetriever())
.map(
urnDefinition ->
STRUCTURED_PROPERTY_MAPPING_FIELD_PREFIX
+ StructuredPropertyUtils.toElasticsearchFieldName(
urnDefinition.getFirst(), urnDefinition.getSecond()))
.orElse(facets.get(i));
String facet = facets.get(i);

AggregationBuilder aggBuilder;
if (facet.contains(AGGREGATION_SPECIAL_TYPE_DELIMITER)) {
Expand Down Expand Up @@ -190,8 +182,10 @@ private String getAggregationField(
// Boolean hasX field, not a keyword field. Return the name of the original facet.
return facet;
}
// Otherwise assume that this field is of keyword type.
return ESUtils.toKeywordField(facet, false, aspectRetriever);
// intercept structured property if it exists
return toStructuredPropertyFacetName(facet, aspectRetriever)
// Otherwise assume that this field is of keyword type.
.orElse(ESUtils.toKeywordField(facet, false, aspectRetriever));
}

List<String> getDefaultFacetFieldsFromAnnotation(final SearchableAnnotation annotation) {
Expand Down
Loading