refactor(platform): Refactoring ES Utils, adding EXISTS condition support to Filter Criterion (#7832)

This commit is contained in:
John Joyce 2023-04-20 09:42:07 -07:00 committed by GitHub
parent 90f653b622
commit 73a12379ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 414 additions and 77 deletions

View File

@ -29,6 +29,9 @@ import static com.linkedin.metadata.search.elasticsearch.query.request.SearchFie
import static com.linkedin.metadata.search.utils.SearchUtils.isUrn;
/**
* TODO: Add more robust unit tests for this critical class.
*/
@Slf4j
public class ESUtils {
@ -135,85 +138,27 @@ public class ESUtils {
* @param criterion {@link Criterion} single criterion which contains field, value and a comparison operator
*/
@Nonnull
public static QueryBuilder getQueryBuilderFromCriterion(@Nonnull Criterion criterion, boolean isTimeseries) {
String fieldName = toFacetField(criterion.getField());
public static QueryBuilder getQueryBuilderFromCriterion(@Nonnull final Criterion criterion, boolean isTimeseries) {
final String fieldName = toFacetField(criterion.getField());
Optional<String[]> pairMatch = Arrays.stream(EDITABLE_FIELD_TO_QUERY_PAIRS)
.filter(pair -> Arrays.stream(pair).anyMatch(pairValue -> pairValue.equals(fieldName)))
/*
* Check the field-name for a "sibling" field, or one which should ALWAYS
* be matched in disjunction with the targeted field (OR).
*
* This essentially equates to filter expansion based on a particular field.
* First we handle this expansion, if required, otherwise we build the filter as usual
* without expansion.
*/
final Optional<String[]> maybeFieldToExpand = Arrays.stream(EDITABLE_FIELD_TO_QUERY_PAIRS)
.filter(pair -> Arrays.asList(pair).contains(fieldName))
.findFirst();
if (pairMatch.isPresent()) {
final BoolQueryBuilder orQueryBuilder = new BoolQueryBuilder();
String[] pairMatchValue = pairMatch.get();
for (String field: pairMatchValue) {
Criterion criterionToQuery = new Criterion();
criterionToQuery.setCondition(criterion.getCondition());
criterionToQuery.setNegated(criterion.isNegated());
criterionToQuery.setValue(criterion.getValue());
criterionToQuery.setValues(criterion.getValues());
criterionToQuery.setField(toKeywordField(field, isTimeseries));
orQueryBuilder.should(getQueryBuilderFromCriterionForSingleField(criterionToQuery, isTimeseries));
}
return orQueryBuilder;
if (maybeFieldToExpand.isPresent()) {
return getQueryBuilderFromCriterionForFieldToExpand(maybeFieldToExpand.get(), criterion, isTimeseries);
}
return getQueryBuilderFromCriterionForSingleField(criterion, isTimeseries);
}
@Nonnull
public static QueryBuilder getQueryBuilderFromCriterionForSingleField(@Nonnull Criterion criterion, @Nonnull boolean isTimeseries) {
final Condition condition = criterion.getCondition();
String fieldName = toFacetField(criterion.getField());
if (condition == Condition.EQUAL) {
// If values is set, use terms query to match one of the values
if (!criterion.getValues().isEmpty()) {
if (BOOLEAN_FIELDS.contains(fieldName) && criterion.getValues().size() == 1) {
return QueryBuilders.termQuery(fieldName, Boolean.parseBoolean(criterion.getValues().get(0)))
.queryName(fieldName);
}
return QueryBuilders.termsQuery(toKeywordField(criterion.getField(), isTimeseries), criterion.getValues())
.queryName(fieldName);
}
// TODO(https://github.com/datahub-project/datahub-gma/issues/51): support multiple values a field can take without using
// delimiters like comma. This is a hack to support equals with URN that has a comma in it.
if (isUrn(criterion.getValue())) {
return QueryBuilders.matchQuery(toKeywordField(criterion.getField(), isTimeseries), criterion.getValue().trim())
.queryName(fieldName)
.analyzer(KEYWORD_ANALYZER);
}
BoolQueryBuilder filters = new BoolQueryBuilder();
// Cannot assume the existence of a .keyword or other subfield (unless contains `.`)
// Cannot assume the type of the underlying field or subfield thus KEYWORD_ANALYZER is forced
List<String> fields = criterion.getField().contains(".") ? List.of(criterion.getField())
: List.of(criterion.getField(), criterion.getField() + ".*");
Arrays.stream(criterion.getValue().trim().split("\\s*,\\s*"))
.forEach(elem -> filters.should(QueryBuilders.multiMatchQuery(elem, fields.toArray(new String[0]))
.queryName(fieldName)
.analyzer(KEYWORD_ANALYZER)));
return filters;
} else if (condition == Condition.IS_NULL) {
return QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(criterion.getField())).queryName(fieldName);
} else if (condition == Condition.GREATER_THAN) {
return QueryBuilders.rangeQuery(criterion.getField()).gt(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.GREATER_THAN_OR_EQUAL_TO) {
return QueryBuilders.rangeQuery(criterion.getField()).gte(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.LESS_THAN) {
return QueryBuilders.rangeQuery(criterion.getField()).lt(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.LESS_THAN_OR_EQUAL_TO) {
return QueryBuilders.rangeQuery(criterion.getField()).lte(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.CONTAIN) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
"*" + ESUtils.escapeReservedCharacters(criterion.getValue().trim()) + "*").queryName(fieldName);
} else if (condition == Condition.START_WITH) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
ESUtils.escapeReservedCharacters(criterion.getValue().trim()) + "*").queryName(fieldName);
} else if (condition == Condition.END_WITH) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
"*" + ESUtils.escapeReservedCharacters(criterion.getValue().trim())).queryName(fieldName);
}
throw new UnsupportedOperationException("Unsupported condition: " + condition);
}
/**
* Populates source field of search query with the sort order as per the criterion provided.
@ -256,7 +201,7 @@ public class ESUtils {
return input;
}
@Nullable
@Nonnull
public static String toFacetField(@Nonnull final String filterField) {
return filterField.replace(ESUtils.KEYWORD_SUFFIX, "");
}
@ -298,4 +243,126 @@ public class ESUtils {
searchSourceBuilder.pointInTimeBuilder(pointInTimeBuilder);
}
}
@Nonnull
private static QueryBuilder getQueryBuilderFromCriterionForFieldToExpand(
@Nonnull final String[] fields,
@Nonnull final Criterion criterion,
final boolean isTimeseries) {
final BoolQueryBuilder orQueryBuilder = new BoolQueryBuilder();
for (String field : fields) {
Criterion criterionToQuery = new Criterion();
criterionToQuery.setCondition(criterion.getCondition());
criterionToQuery.setNegated(criterion.isNegated());
criterionToQuery.setValue(criterion.getValue());
criterionToQuery.setValues(criterion.getValues());
criterionToQuery.setField(toKeywordField(field, isTimeseries));
orQueryBuilder.should(getQueryBuilderFromCriterionForSingleField(criterionToQuery, isTimeseries));
}
return orQueryBuilder;
}
@Nonnull
private static QueryBuilder getQueryBuilderFromCriterionForSingleField(@Nonnull Criterion criterion, @Nonnull boolean isTimeseries) {
final Condition condition = criterion.getCondition();
final String fieldName = toFacetField(criterion.getField());
if (condition == Condition.EQUAL) {
return buildEqualsConditionFromCriterion(fieldName, criterion, isTimeseries);
} else if (condition == Condition.IS_NULL) {
return QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(criterion.getField())).queryName(fieldName);
} else if (condition == Condition.EXISTS) {
return QueryBuilders.boolQuery().must(QueryBuilders.existsQuery(criterion.getField())).queryName(fieldName);
// TODO: Support multi-match on the following operators (using new 'values' field)
} else if (condition == Condition.GREATER_THAN) {
return QueryBuilders.rangeQuery(criterion.getField()).gt(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.GREATER_THAN_OR_EQUAL_TO) {
return QueryBuilders.rangeQuery(criterion.getField()).gte(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.LESS_THAN) {
return QueryBuilders.rangeQuery(criterion.getField()).lt(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.LESS_THAN_OR_EQUAL_TO) {
return QueryBuilders.rangeQuery(criterion.getField()).lte(criterion.getValue().trim()).queryName(fieldName);
} else if (condition == Condition.CONTAIN) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
"*" + ESUtils.escapeReservedCharacters(criterion.getValue().trim()) + "*").queryName(fieldName);
} else if (condition == Condition.START_WITH) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
ESUtils.escapeReservedCharacters(criterion.getValue().trim()) + "*").queryName(fieldName);
} else if (condition == Condition.END_WITH) {
return QueryBuilders.wildcardQuery(toKeywordField(criterion.getField(), isTimeseries),
"*" + ESUtils.escapeReservedCharacters(criterion.getValue().trim())).queryName(fieldName);
}
throw new UnsupportedOperationException("Unsupported condition: " + condition);
}
private static QueryBuilder buildEqualsConditionFromCriterion(
@Nonnull final String fieldName,
@Nonnull final Criterion criterion,
final boolean isTimeseries) {
/*
* If the newer 'values' field of Criterion.pdl is set, then we
* handle using the following code to allow multi-match.
*/
if (!criterion.getValues().isEmpty()) {
return buildEqualsConditionFromCriterionWithValues(fieldName, criterion, isTimeseries);
}
/*
* Otherwise, we are likely using the deprecated 'value' field.
* We handle using the legacy code path below.
*/
return buildEqualsFromCriterionWithValue(fieldName, criterion, isTimeseries);
}
/**
* Builds an instance of {@link QueryBuilder} representing an EQUALS condition which
* was created using the new multi-match 'values' field of Criterion.pdl model.
*/
private static QueryBuilder buildEqualsConditionFromCriterionWithValues(
@Nonnull final String fieldName,
@Nonnull final Criterion criterion,
final boolean isTimeseries) {
if (BOOLEAN_FIELDS.contains(fieldName) && criterion.getValues().size() == 1) {
// Handle special-cased Boolean fields.
// here we special case boolean fields we recognize the names of and hard-cast
// the first provided value to a boolean to do the comparison.
// Ideally, we should detect the type of the field from the entity-registry in order
// to determine how to cast.
return QueryBuilders.termQuery(fieldName, Boolean.parseBoolean(criterion.getValues().get(0)))
.queryName(fieldName);
}
return QueryBuilders.termsQuery(toKeywordField(criterion.getField(), isTimeseries), criterion.getValues())
.queryName(fieldName);
}
/**
* Builds an instance of {@link QueryBuilder} representing an EQUALS condition which
* was created using the deprecated 'value' field of Criterion.pdl model.
*
* Previously, we supported comma-separate values inside of a single string field,
* thus we have to account for splitting and matching against each value below.
*
* For all new code, we should be using the new 'values' field for performing multi-match. This
* is simply retained for backwards compatibility of the search API.
*/
private static QueryBuilder buildEqualsFromCriterionWithValue(
@Nonnull final String fieldName,
@Nonnull final Criterion criterion,
final boolean isTimeseries) {
// If the value is an URN style value, then we do not attempt to split it by comma (for obvious reasons)
if (isUrn(criterion.getValue())) {
return QueryBuilders.matchQuery(toKeywordField(criterion.getField(), isTimeseries), criterion.getValue().trim())
.queryName(fieldName)
.analyzer(KEYWORD_ANALYZER);
}
final BoolQueryBuilder filters = new BoolQueryBuilder();
// Cannot assume the existence of a .keyword or other subfield (unless contains `.`)
// Cannot assume the type of the underlying field or subfield thus KEYWORD_ANALYZER is forced
List<String> fields = criterion.getField().contains(".") ? List.of(criterion.getField())
: List.of(criterion.getField(), criterion.getField() + ".*");
Arrays.stream(criterion.getValue().trim().split("\\s*,\\s*"))
.forEach(elem -> filters.should(QueryBuilders.multiMatchQuery(elem, fields.toArray(new String[0]))
.queryName(fieldName)
.analyzer(KEYWORD_ANALYZER)));
return filters;
}
}

View File

@ -0,0 +1,262 @@
package com.linkedin.metadata.search.utils;
import com.google.common.collect.ImmutableList;
import com.linkedin.data.template.StringArray;
import com.linkedin.metadata.query.filter.Condition;
import com.linkedin.metadata.query.filter.Criterion;
import org.elasticsearch.index.query.QueryBuilder;
import org.testng.Assert;
import org.testng.annotations.Test;
public class ESUtilsTest {
private static final String FIELD_TO_EXPAND = "fieldTags";
@Test
public void testGetQueryBuilderFromCriterionEqualsValues() {
final Criterion singleValueCriterion = new Criterion()
.setField("myTestField")
.setCondition(Condition.EQUAL)
.setValues(new StringArray(ImmutableList.of(
"value1"
)));
QueryBuilder result = ESUtils.getQueryBuilderFromCriterion(singleValueCriterion, false);
String expected =
"{\n"
+ " \"terms\" : {\n"
+ " \"myTestField.keyword\" : [\n"
+ " \"value1\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
final Criterion multiValueCriterion = new Criterion()
.setField("myTestField")
.setCondition(Condition.EQUAL)
.setValues(new StringArray(ImmutableList.of(
"value1", "value2"
)));
result = ESUtils.getQueryBuilderFromCriterion(multiValueCriterion, false);
expected =
"{\n"
+ " \"terms\" : {\n"
+ " \"myTestField.keyword\" : [\n"
+ " \"value1\",\n"
+ " \"value2\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
final Criterion timeseriesField = new Criterion()
.setField("myTestField")
.setCondition(Condition.EQUAL)
.setValues(new StringArray(ImmutableList.of(
"value1", "value2"
)));
result = ESUtils.getQueryBuilderFromCriterion(timeseriesField, true);
expected = "{\n"
+ " \"terms\" : {\n"
+ " \"myTestField\" : [\n"
+ " \"value1\",\n"
+ " \"value2\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
}
@Test
public void testGetQueryBuilderFromCriterionExists() {
final Criterion singleValueCriterion = new Criterion()
.setField("myTestField")
.setCondition(Condition.EXISTS);
QueryBuilder result = ESUtils.getQueryBuilderFromCriterion(singleValueCriterion, false);
String expected =
"{\n"
+ " \"bool\" : {\n"
+ " \"must\" : [\n"
+ " {\n"
+ " \"exists\" : {\n"
+ " \"field\" : \"myTestField\",\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
// No diff in the timeseries field case for this condition.
final Criterion timeseriesField = new Criterion()
.setField("myTestField")
.setCondition(Condition.EXISTS);
result = ESUtils.getQueryBuilderFromCriterion(timeseriesField, true);
expected = "{\n"
+ " \"bool\" : {\n"
+ " \"must\" : [\n"
+ " {\n"
+ " \"exists\" : {\n"
+ " \"field\" : \"myTestField\",\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
}
@Test
public void testGetQueryBuilderFromCriterionIsNull() {
final Criterion singleValueCriterion = new Criterion()
.setField("myTestField")
.setCondition(Condition.IS_NULL);
QueryBuilder result = ESUtils.getQueryBuilderFromCriterion(singleValueCriterion, false);
String expected =
"{\n"
+ " \"bool\" : {\n"
+ " \"must_not\" : [\n"
+ " {\n"
+ " \"exists\" : {\n"
+ " \"field\" : \"myTestField\",\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
// No diff in the timeseries case for this condition
final Criterion timeseriesField = new Criterion()
.setField("myTestField")
.setCondition(Condition.IS_NULL);
result = ESUtils.getQueryBuilderFromCriterion(timeseriesField, true);
expected = "{\n"
+ " \"bool\" : {\n"
+ " \"must_not\" : [\n"
+ " {\n"
+ " \"exists\" : {\n"
+ " \"field\" : \"myTestField\",\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"myTestField\"\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
}
@Test
public void testGetQueryBuilderFromCriterionFieldToExpand() {
final Criterion singleValueCriterion = new Criterion()
.setField(FIELD_TO_EXPAND)
.setCondition(Condition.EQUAL)
.setValue("") // Ignored
.setValues(new StringArray(ImmutableList.of(
"value1"
)));
// Ensure that the query is expanded!
QueryBuilder result = ESUtils.getQueryBuilderFromCriterion(singleValueCriterion, false);
String expected = "{\n"
+ " \"bool\" : {\n"
+ " \"should\" : [\n"
+ " {\n"
+ " \"terms\" : {\n"
+ " \"fieldTags.keyword\" : [\n"
+ " \"value1\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"fieldTags\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"terms\" : {\n"
+ " \"editedFieldTags.keyword\" : [\n"
+ " \"value1\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"editedFieldTags\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
final Criterion timeseriesField = new Criterion()
.setField(FIELD_TO_EXPAND)
.setCondition(Condition.EQUAL)
.setValue("") // Ignored
.setValues(new StringArray(ImmutableList.of(
"value1", "value2"
)));
// Ensure that the query is expanded without keyword.
result = ESUtils.getQueryBuilderFromCriterion(timeseriesField, true);
expected =
"{\n"
+ " \"bool\" : {\n"
+ " \"should\" : [\n"
+ " {\n"
+ " \"terms\" : {\n"
+ " \"fieldTags\" : [\n"
+ " \"value1\",\n"
+ " \"value2\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"fieldTags\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"terms\" : {\n"
+ " \"editedFieldTags\" : [\n"
+ " \"value1\",\n"
+ " \"value2\"\n"
+ " ],\n"
+ " \"boost\" : 1.0,\n"
+ " \"_name\" : \"editedFieldTags\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"adjust_pure_negative\" : true,\n"
+ " \"boost\" : 1.0\n"
+ " }\n"
+ "}";
Assert.assertEquals(result.toString(), expected);
}
}

View File

@ -20,11 +20,16 @@ enum Condition {
*/
EQUAL
/**
/**
* Represent the relation: field is null, e.g. platform is null
*/
IS_NULL
/**
* Represents the relation: field exists and is non-empty, e.g. owners is not null and != [] (empty)
*/
EXISTS
/**
* Represent the relation greater than, e.g. ownerCount > 5
*/

View File

@ -56,11 +56,12 @@
"type" : "enum",
"name" : "Condition",
"doc" : "The matching condition in a filter criterion",
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "EXISTS", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbolDocs" : {
"CONTAIN" : "Represent the relation: String field contains value, e.g. name contains Profile",
"END_WITH" : "Represent the relation: String field ends with value, e.g. name ends with Event",
"EQUAL" : "Represent the relation: field = value, e.g. platform = hdfs",
"EXISTS" : "Represents the relation: field exists and is non-empty, e.g. owners is not null and != [] (empty)",
"GREATER_THAN" : "Represent the relation greater than, e.g. ownerCount > 5",
"GREATER_THAN_OR_EQUAL_TO" : "Represent the relation greater than or equal to, e.g. ownerCount >= 5",
"IN" : "Represent the relation: String field is one of the array values to, e.g. name in [\"Profile\", \"Event\"]",

View File

@ -146,11 +146,12 @@
"type" : "enum",
"name" : "Condition",
"doc" : "The matching condition in a filter criterion",
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "EXISTS", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbolDocs" : {
"CONTAIN" : "Represent the relation: String field contains value, e.g. name contains Profile",
"END_WITH" : "Represent the relation: String field ends with value, e.g. name ends with Event",
"EQUAL" : "Represent the relation: field = value, e.g. platform = hdfs",
"EXISTS" : "Represents the relation: field exists and is non-empty, e.g. owners is not null and != [] (empty)",
"GREATER_THAN" : "Represent the relation greater than, e.g. ownerCount > 5",
"GREATER_THAN_OR_EQUAL_TO" : "Represent the relation greater than or equal to, e.g. ownerCount >= 5",
"IN" : "Represent the relation: String field is one of the array values to, e.g. name in [\"Profile\", \"Event\"]",

View File

@ -5637,11 +5637,12 @@
"name" : "Condition",
"namespace" : "com.linkedin.metadata.query.filter",
"doc" : "The matching condition in a filter criterion",
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbols" : [ "CONTAIN", "END_WITH", "EQUAL", "IS_NULL", "EXISTS", "GREATER_THAN", "GREATER_THAN_OR_EQUAL_TO", "IN", "LESS_THAN", "LESS_THAN_OR_EQUAL_TO", "START_WITH" ],
"symbolDocs" : {
"CONTAIN" : "Represent the relation: String field contains value, e.g. name contains Profile",
"END_WITH" : "Represent the relation: String field ends with value, e.g. name ends with Event",
"EQUAL" : "Represent the relation: field = value, e.g. platform = hdfs",
"EXISTS" : "Represents the relation: field exists and is non-empty, e.g. owners is not null and != [] (empty)",
"GREATER_THAN" : "Represent the relation greater than, e.g. ownerCount > 5",
"GREATER_THAN_OR_EQUAL_TO" : "Represent the relation greater than or equal to, e.g. ownerCount >= 5",
"IN" : "Represent the relation: String field is one of the array values to, e.g. name in [\"Profile\", \"Event\"]",