Skip to content

Commit 20b9050

Browse files
authored
fix(browsev2): align browse and aggregate queries (#9790)
1 parent ea0ae8c commit 20b9050

File tree

11 files changed

+99
-35
lines changed

11 files changed

+99
-35
lines changed

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/chart/BrowseV2Resolver.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.linkedin.datahub.graphql.types.entitytype.EntityTypeMapper;
1919
import com.linkedin.entity.client.EntityClient;
2020
import com.linkedin.metadata.browse.BrowseResultV2;
21+
import com.linkedin.metadata.query.SearchFlags;
2122
import com.linkedin.metadata.query.filter.Filter;
2223
import com.linkedin.metadata.service.FormService;
2324
import com.linkedin.metadata.service.ViewService;
@@ -52,6 +53,7 @@ public CompletableFuture<BrowseResultsV2> get(DataFetchingEnvironment environmen
5253
final int start = input.getStart() != null ? input.getStart() : DEFAULT_START;
5354
final int count = input.getCount() != null ? input.getCount() : DEFAULT_COUNT;
5455
final String query = input.getQuery() != null ? input.getQuery() : "*";
56+
final SearchFlags searchFlags = mapInputFlags(input.getSearchFlags());
5557
// escape forward slash since it is a reserved character in Elasticsearch
5658
final String sanitizedQuery = ResolverUtils.escapeForwardSlash(query);
5759

@@ -83,7 +85,8 @@ public CompletableFuture<BrowseResultsV2> get(DataFetchingEnvironment environmen
8385
sanitizedQuery,
8486
start,
8587
count,
86-
context.getAuthentication());
88+
context.getAuthentication(),
89+
searchFlags);
8790
return mapBrowseResults(browseResults);
8891
} catch (Exception e) {
8992
throw new RuntimeException("Failed to execute browse V2", e);

datahub-graphql-core/src/main/resources/search.graphql

+5
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,11 @@ input BrowseV2Input {
12301230
The search query string
12311231
"""
12321232
query: String
1233+
1234+
"""
1235+
Flags controlling search options
1236+
"""
1237+
searchFlags: SearchFlags
12331238
}
12341239

12351240
"""

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/browse/BrowseV2ResolverTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.linkedin.metadata.browse.BrowseResultGroupV2Array;
2222
import com.linkedin.metadata.browse.BrowseResultMetadata;
2323
import com.linkedin.metadata.browse.BrowseResultV2;
24+
import com.linkedin.metadata.query.SearchFlags;
2425
import com.linkedin.metadata.query.filter.ConjunctiveCriterion;
2526
import com.linkedin.metadata.query.filter.ConjunctiveCriterionArray;
2627
import com.linkedin.metadata.query.filter.Criterion;
@@ -262,7 +263,8 @@ private static EntityClient initMockEntityClient(
262263
Mockito.eq(query),
263264
Mockito.eq(start),
264265
Mockito.eq(limit),
265-
Mockito.any(Authentication.class)))
266+
Mockito.any(Authentication.class),
267+
Mockito.nullable(SearchFlags.class)))
266268
.thenReturn(result);
267269
return client;
268270
}

metadata-io/src/main/java/com/linkedin/metadata/client/JavaEntityClient.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,11 @@ public BrowseResultV2 browseV2(
229229
@Nonnull String input,
230230
int start,
231231
int count,
232-
@Nonnull Authentication authentication) {
232+
@Nonnull Authentication authentication,
233+
@Nullable SearchFlags searchFlags) {
233234
// TODO: cache browseV2 results
234-
return _entitySearchService.browseV2(entityName, path, filter, input, start, count);
235+
return _entitySearchService.browseV2(
236+
entityName, path, filter, input, start, count, searchFlags);
235237
}
236238

237239
/**
@@ -253,9 +255,11 @@ public BrowseResultV2 browseV2(
253255
@Nonnull String input,
254256
int start,
255257
int count,
256-
@Nonnull Authentication authentication) {
258+
@Nonnull Authentication authentication,
259+
@Nullable SearchFlags searchFlags) {
257260
// TODO: cache browseV2 results
258-
return _entitySearchService.browseV2(entityNames, path, filter, input, start, count);
261+
return _entitySearchService.browseV2(
262+
entityNames, path, filter, input, start, count, searchFlags);
259263
}
260264

261265
@SneakyThrows

metadata-io/src/main/java/com/linkedin/metadata/search/elasticsearch/ElasticSearchService.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,9 @@ public BrowseResultV2 browseV2(
215215
@Nullable Filter filter,
216216
@Nonnull String input,
217217
int start,
218-
int count) {
219-
return esBrowseDAO.browseV2(entityName, path, filter, input, start, count);
218+
int count,
219+
@Nullable SearchFlags searchFlags) {
220+
return esBrowseDAO.browseV2(entityName, path, filter, input, start, count, searchFlags);
220221
}
221222

222223
@Nonnull
@@ -227,8 +228,9 @@ public BrowseResultV2 browseV2(
227228
@Nullable Filter filter,
228229
@Nonnull String input,
229230
int start,
230-
int count) {
231-
return esBrowseDAO.browseV2(entityNames, path, filter, input, start, count);
231+
int count,
232+
@Nullable SearchFlags searchFlags) {
233+
return esBrowseDAO.browseV2(entityNames, path, filter, input, start, count, searchFlags);
232234
}
233235

234236
@Nonnull

metadata-io/src/main/java/com/linkedin/metadata/search/elasticsearch/query/ESBrowseDAO.java

+26-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.linkedin.metadata.models.EntitySpec;
2222
import com.linkedin.metadata.models.annotation.SearchableAnnotation;
2323
import com.linkedin.metadata.models.registry.EntityRegistry;
24+
import com.linkedin.metadata.query.SearchFlags;
2425
import com.linkedin.metadata.query.filter.Filter;
2526
import com.linkedin.metadata.search.elasticsearch.query.request.SearchRequestHandler;
2627
import com.linkedin.metadata.search.utils.ESUtils;
@@ -34,6 +35,7 @@
3435
import java.util.Collections;
3536
import java.util.List;
3637
import java.util.Map;
38+
import java.util.Optional;
3739
import java.util.Set;
3840
import java.util.stream.Collectors;
3941
import javax.annotation.Nonnull;
@@ -399,14 +401,15 @@ public BrowseResultV2 browseV2(
399401
@Nullable Filter filter,
400402
@Nonnull String input,
401403
int start,
402-
int count) {
404+
int count,
405+
@Nullable SearchFlags searchFlags) {
403406
try {
404407
final SearchResponse groupsResponse;
405408
try (Timer.Context ignored = MetricUtils.timer(this.getClass(), "esGroupSearch").time()) {
406409
final String finalInput = input.isEmpty() ? "*" : input;
407410
groupsResponse =
408411
client.search(
409-
constructGroupsSearchRequestV2(entityName, path, filter, finalInput),
412+
constructGroupsSearchRequestV2(entityName, path, filter, finalInput, searchFlags),
410413
RequestOptions.DEFAULT);
411414
}
412415

@@ -435,7 +438,8 @@ public BrowseResultV2 browseV2(
435438
@Nullable Filter filter,
436439
@Nonnull String input,
437440
int start,
438-
int count) {
441+
int count,
442+
@Nullable SearchFlags searchFlags) {
439443
try {
440444
final SearchResponse groupsResponse;
441445

@@ -444,7 +448,7 @@ public BrowseResultV2 browseV2(
444448
groupsResponse =
445449
client.search(
446450
constructGroupsSearchRequestBrowseAcrossEntities(
447-
entities, path, filter, finalInput),
451+
entities, path, filter, finalInput, searchFlags),
448452
RequestOptions.DEFAULT);
449453
}
450454

@@ -472,7 +476,8 @@ private SearchRequest constructGroupsSearchRequestV2(
472476
@Nonnull String entityName,
473477
@Nonnull String path,
474478
@Nullable Filter filter,
475-
@Nonnull String input) {
479+
@Nonnull String input,
480+
@Nullable SearchFlags searchFlags) {
476481
final String indexName = indexConvention.getIndexName(entityRegistry.getEntitySpec(entityName));
477482
final SearchRequest searchRequest = new SearchRequest(indexName);
478483
final SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
@@ -482,7 +487,8 @@ private SearchRequest constructGroupsSearchRequestV2(
482487
entityName,
483488
path,
484489
SearchUtil.transformFilterForEntities(filter, indexConvention),
485-
input));
490+
input,
491+
searchFlags));
486492
searchSourceBuilder.aggregation(buildAggregationsV2(path));
487493
searchRequest.source(searchSourceBuilder);
488494
return searchRequest;
@@ -493,7 +499,8 @@ private SearchRequest constructGroupsSearchRequestBrowseAcrossEntities(
493499
@Nonnull List<String> entities,
494500
@Nonnull String path,
495501
@Nullable Filter filter,
496-
@Nonnull String input) {
502+
@Nonnull String input,
503+
@Nullable SearchFlags searchFlags) {
497504

498505
List<EntitySpec> entitySpecs =
499506
entities.stream().map(entityRegistry::getEntitySpec).collect(Collectors.toList());
@@ -509,7 +516,8 @@ private SearchRequest constructGroupsSearchRequestBrowseAcrossEntities(
509516
entitySpecs,
510517
path,
511518
SearchUtil.transformFilterForEntities(filter, indexConvention),
512-
input));
519+
input,
520+
searchFlags));
513521
searchSourceBuilder.aggregation(buildAggregationsV2(path));
514522
searchRequest.source(searchSourceBuilder);
515523
return searchRequest;
@@ -537,15 +545,18 @@ private QueryBuilder buildQueryStringV2(
537545
@Nonnull String entityName,
538546
@Nonnull String path,
539547
@Nullable Filter filter,
540-
@Nonnull String input) {
548+
@Nonnull String input,
549+
@Nullable SearchFlags searchFlags) {
550+
SearchFlags finalSearchFlags =
551+
Optional.ofNullable(searchFlags).orElse(new SearchFlags().setFulltext(true));
541552
final int browseDepthVal = getPathDepthV2(path);
542553

543554
final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
544555

545556
EntitySpec entitySpec = entityRegistry.getEntitySpec(entityName);
546557
QueryBuilder query =
547558
SearchRequestHandler.getBuilder(entitySpec, searchConfiguration, customSearchConfiguration)
548-
.getQuery(input, false);
559+
.getQuery(input, Boolean.TRUE.equals(finalSearchFlags.isFulltext()));
549560
queryBuilder.must(query);
550561

551562
filterSoftDeletedByDefault(filter, queryBuilder);
@@ -567,14 +578,17 @@ private QueryBuilder buildQueryStringBrowseAcrossEntities(
567578
@Nonnull List<EntitySpec> entitySpecs,
568579
@Nonnull String path,
569580
@Nullable Filter filter,
570-
@Nonnull String input) {
581+
@Nonnull String input,
582+
@Nullable SearchFlags searchFlags) {
583+
SearchFlags finalSearchFlags =
584+
Optional.ofNullable(searchFlags).orElse(new SearchFlags().setFulltext(true));
571585
final int browseDepthVal = getPathDepthV2(path);
572586

573587
final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
574588

575589
QueryBuilder query =
576590
SearchRequestHandler.getBuilder(entitySpecs, searchConfiguration, customSearchConfiguration)
577-
.getQuery(input, false);
591+
.getQuery(input, Boolean.TRUE.equals(finalSearchFlags.isFulltext()));
578592
queryBuilder.must(query);
579593

580594
if (!path.isEmpty()) {

metadata-io/src/main/java/com/linkedin/metadata/search/elasticsearch/query/request/SearchQueryBuilder.java

+26-7
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,10 @@ private QueryBuilder buildInternalQuery(
135135
query.startsWith(STRUCTURED_QUERY_PREFIX)
136136
? query.substring(STRUCTURED_QUERY_PREFIX.length())
137137
: query;
138-
139-
QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery(withoutQueryPrefix);
140-
queryBuilder.defaultOperator(Operator.AND);
141-
getStandardFields(entitySpecs)
142-
.forEach(entitySpec -> queryBuilder.field(entitySpec.fieldName(), entitySpec.boost()));
143-
finalQuery.should(queryBuilder);
138+
getStructuredQuery(customQueryConfig, entitySpecs, withoutQueryPrefix)
139+
.ifPresent(finalQuery::should);
144140
if (exactMatchConfiguration.isEnableStructured()) {
145-
getPrefixAndExactMatchQuery(null, entitySpecs, withoutQueryPrefix)
141+
getPrefixAndExactMatchQuery(customQueryConfig, entitySpecs, withoutQueryPrefix)
146142
.ifPresent(finalQuery::should);
147143
}
148144
}
@@ -415,6 +411,29 @@ private Optional<QueryBuilder> getPrefixAndExactMatchQuery(
415411
return finalQuery.should().size() > 0 ? Optional.of(finalQuery) : Optional.empty();
416412
}
417413

414+
private Optional<QueryBuilder> getStructuredQuery(
415+
@Nullable QueryConfiguration customQueryConfig,
416+
List<EntitySpec> entitySpecs,
417+
String sanitizedQuery) {
418+
Optional<QueryBuilder> result = Optional.empty();
419+
420+
final boolean executeStructuredQuery;
421+
if (customQueryConfig != null) {
422+
executeStructuredQuery = customQueryConfig.isStructuredQuery();
423+
} else {
424+
executeStructuredQuery = !(isQuoted(sanitizedQuery) && exactMatchConfiguration.isExclusive());
425+
}
426+
427+
if (executeStructuredQuery) {
428+
QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery(sanitizedQuery);
429+
queryBuilder.defaultOperator(Operator.AND);
430+
getStandardFields(entitySpecs)
431+
.forEach(entitySpec -> queryBuilder.field(entitySpec.fieldName(), entitySpec.boost()));
432+
result = Optional.of(queryBuilder);
433+
}
434+
return result;
435+
}
436+
418437
private FunctionScoreQueryBuilder buildScoreFunctions(
419438
@Nullable QueryConfiguration customQueryConfig,
420439
@Nonnull List<EntitySpec> entitySpecs,

metadata-service/configuration/src/main/java/com/linkedin/metadata/config/search/custom/QueryConfiguration.java

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public class QueryConfiguration {
1919

2020
private String queryRegex;
2121
@Builder.Default private boolean simpleQuery = true;
22+
23+
/**
24+
* Used to determine if standard structured query logic should be applied when relevant, i.e.
25+
* fullText flag is false. Will not be added in cases where simpleQuery would be the standard.
26+
*/
27+
@Builder.Default private boolean structuredQuery = true;
28+
2229
@Builder.Default private boolean exactMatchQuery = true;
2330
@Builder.Default private boolean prefixMatchQuery = true;
2431
private BoolQueryConfiguration boolQuery;

metadata-service/restli-client/src/main/java/com/linkedin/entity/client/EntityClient.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ public BrowseResultV2 browseV2(
154154
@Nonnull String input,
155155
int start,
156156
int count,
157-
@Nonnull Authentication authentication)
157+
@Nonnull Authentication authentication,
158+
@Nullable SearchFlags searchFlags)
158159
throws RemoteInvocationException;
159160

160161
/**
@@ -176,7 +177,8 @@ public BrowseResultV2 browseV2(
176177
@Nonnull String input,
177178
int start,
178179
int count,
179-
@Nonnull Authentication authentication)
180+
@Nonnull Authentication authentication,
181+
@Nullable SearchFlags searchFlags)
180182
throws RemoteInvocationException;
181183

182184
@Deprecated

metadata-service/restli-client/src/main/java/com/linkedin/entity/client/RestliEntityClient.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ public BrowseResultV2 browseV2(
378378
@Nonnull String input,
379379
int start,
380380
int count,
381-
@Nonnull Authentication authentication) {
381+
@Nonnull Authentication authentication,
382+
@Nullable SearchFlags searchFlags) {
382383
throw new NotImplementedException("BrowseV2 is not implemented in Restli yet");
383384
}
384385

@@ -391,7 +392,8 @@ public BrowseResultV2 browseV2(
391392
@Nonnull String input,
392393
int start,
393394
int count,
394-
@Nonnull Authentication authentication)
395+
@Nonnull Authentication authentication,
396+
@Nullable SearchFlags searchFlags)
395397
throws RemoteInvocationException {
396398
throw new NotImplementedException("BrowseV2 is not implemented in Restli yet");
397399
}

metadata-service/services/src/main/java/com/linkedin/metadata/search/EntitySearchService.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ BrowseResult browse(
197197
* @param input search query
198198
* @param start start offset of first group
199199
* @param count max number of results requested
200+
* @param searchFlags configuration options for search
200201
*/
201202
@Nonnull
202203
public BrowseResultV2 browseV2(
@@ -205,7 +206,8 @@ public BrowseResultV2 browseV2(
205206
@Nullable Filter filter,
206207
@Nonnull String input,
207208
int start,
208-
int count);
209+
int count,
210+
@Nullable SearchFlags searchFlags);
209211

210212
/**
211213
* Gets browse snapshot of a given path
@@ -216,6 +218,7 @@ public BrowseResultV2 browseV2(
216218
* @param input search query
217219
* @param start start offset of first group
218220
* @param count max number of results requested
221+
* @param searchFlags configuration options for search
219222
*/
220223
@Nonnull
221224
public BrowseResultV2 browseV2(
@@ -224,7 +227,8 @@ public BrowseResultV2 browseV2(
224227
@Nullable Filter filter,
225228
@Nonnull String input,
226229
int start,
227-
int count);
230+
int count,
231+
@Nullable SearchFlags searchFlags);
228232

229233
/**
230234
* Gets a list of paths for a given urn.

0 commit comments

Comments
 (0)