fix: support for non-string types in object fields (#11066)

Co-authored-by: david-leifker <114954101+david-leifker@users.noreply.github.com>
Co-authored-by: milindgupta <milindgupta9@github.com>
Co-authored-by: Harshal Sheth <hsheth2@gmail.com>
Co-authored-by: Hyejin Yoon <0327jane@gmail.com>
Co-authored-by: milindgupta9 <133847413+milindgupta9@users.noreply.github.com>
This commit is contained in:
Vigneshwaran Mathiyalagan 2024-09-17 19:26:46 +05:30 committed by GitHub
parent a483172078
commit c2977d8626
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 178 additions and 25 deletions

View File

@ -189,7 +189,7 @@ public class EntitySpecBuilderTest {
testEntityInfo.getPegasusSchema().getFullName()); testEntityInfo.getPegasusSchema().getFullName());
// Assert on Searchable Fields // Assert on Searchable Fields
assertEquals(testEntityInfo.getSearchableFieldSpecs().size(), 12); assertEquals(testEntityInfo.getSearchableFieldSpecs().size(), 17);
assertEquals( assertEquals(
"customProperties", "customProperties",
testEntityInfo testEntityInfo

View File

@ -14,6 +14,7 @@ import com.linkedin.common.AuditStamp;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;
import com.linkedin.data.DataMap; import com.linkedin.data.DataMap;
import com.linkedin.data.schema.DataSchema; import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.MapDataSchema;
import com.linkedin.data.template.RecordTemplate; import com.linkedin.data.template.RecordTemplate;
import com.linkedin.entity.Aspect; import com.linkedin.entity.Aspect;
import com.linkedin.events.metadata.ChangeType; import com.linkedin.events.metadata.ChangeType;
@ -290,8 +291,39 @@ public class SearchDocumentTransformer {
String key = keyValues[0], value = ""; String key = keyValues[0], value = "";
if (keyValues.length > 1) { if (keyValues.length > 1) {
value = keyValues[1]; value = keyValues[1];
if (((MapDataSchema) fieldSpec.getPegasusSchema())
.getValues()
.getType()
.equals(DataSchema.Type.BOOLEAN)) {
dictDoc.set(
key, JsonNodeFactory.instance.booleanNode(Boolean.parseBoolean(value)));
} else if (((MapDataSchema) fieldSpec.getPegasusSchema())
.getValues()
.getType()
.equals(DataSchema.Type.INT)) {
dictDoc.set(key, JsonNodeFactory.instance.numberNode(Integer.parseInt(value)));
} else if (((MapDataSchema) fieldSpec.getPegasusSchema())
.getValues()
.getType()
.equals(DataSchema.Type.DOUBLE)) {
dictDoc.set(
key, JsonNodeFactory.instance.numberNode(Double.parseDouble(value)));
} else if (((MapDataSchema) fieldSpec.getPegasusSchema())
.getValues()
.getType()
.equals(DataSchema.Type.LONG)) {
dictDoc.set(key, JsonNodeFactory.instance.numberNode(Long.parseLong(value)));
} else if (((MapDataSchema) fieldSpec.getPegasusSchema())
.getValues()
.getType()
.equals(DataSchema.Type.FLOAT)) {
dictDoc.set(key, JsonNodeFactory.instance.numberNode(Float.parseFloat(value)));
} else {
dictDoc.put(key, value);
}
} else {
dictDoc.put(key, value);
} }
dictDoc.put(key, value);
}); });
searchDocument.set(fieldName, dictDoc); searchDocument.set(fieldName, dictDoc);
} else if (!fieldValues.isEmpty()) { } else if (!fieldValues.isEmpty()) {

View File

@ -18,6 +18,11 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.linkedin.common.urn.TestEntityUrn; import com.linkedin.common.urn.TestEntityUrn;
import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.Urn;
import com.linkedin.data.template.BooleanMap;
import com.linkedin.data.template.DoubleMap;
import com.linkedin.data.template.FloatMap;
import com.linkedin.data.template.IntegerMap;
import com.linkedin.data.template.LongMap;
import com.linkedin.data.template.StringArray; import com.linkedin.data.template.StringArray;
import com.linkedin.data.template.StringMap; import com.linkedin.data.template.StringMap;
@ -72,6 +77,13 @@ public class TestEntityUtil {
"longValue", "longValue",
"0123456789"))); "0123456789")));
testEntityInfo.setDoubleField(100.456); testEntityInfo.setDoubleField(100.456);
testEntityInfo.setEsObjectFieldBoolean(
new BooleanMap(ImmutableMap.of("key1", true, "key2", false)));
testEntityInfo.setEsObjectFieldLong(new LongMap(ImmutableMap.of("key1", 1L, "key2", 2L)));
testEntityInfo.setEsObjectFieldFloat(new FloatMap(ImmutableMap.of("key1", 1.0f, "key2", 2.0f)));
testEntityInfo.setEsObjectFieldDouble(new DoubleMap(ImmutableMap.of("key1", 1.2, "key2", 2.4)));
testEntityInfo.setEsObjectFieldInteger(
new IntegerMap(ImmutableMap.of("key1", 123, "key2", 456)));
return testEntityInfo; return testEntityInfo;
} }

View File

@ -31,7 +31,7 @@ public class MappingsBuilderTest {
Map<String, Object> result = MappingsBuilder.getMappings(TestEntitySpecBuilder.getSpec()); Map<String, Object> result = MappingsBuilder.getMappings(TestEntitySpecBuilder.getSpec());
assertEquals(result.size(), 1); assertEquals(result.size(), 1);
Map<String, Object> properties = (Map<String, Object>) result.get("properties"); Map<String, Object> properties = (Map<String, Object>) result.get("properties");
assertEquals(properties.size(), 22); assertEquals(properties.size(), 27);
assertEquals( assertEquals(
properties.get("urn"), properties.get("urn"),
ImmutableMap.of( ImmutableMap.of(

View File

@ -111,19 +111,22 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
assertEquals(keywordQuery.value(), "testQuery"); assertEquals(keywordQuery.value(), "testQuery");
assertEquals(keywordQuery.analyzer(), "keyword"); assertEquals(keywordQuery.analyzer(), "keyword");
Map<String, Float> keywordFields = keywordQuery.fields(); Map<String, Float> keywordFields = keywordQuery.fields();
assertEquals(keywordFields.size(), 9); assertEquals(keywordFields.size(), 14);
assertEquals(
keywordFields, assertEquals(keywordFields.get("urn"), 10);
Map.of( assertEquals(keywordFields.get("textArrayField"), 1);
"urn", 10.f, assertEquals(keywordFields.get("customProperties"), 1);
"textArrayField", 1.0f, assertEquals(keywordFields.get("wordGramField"), 1);
"customProperties", 1.0f, assertEquals(keywordFields.get("nestedArrayArrayField"), 1);
"wordGramField", 1.0f, assertEquals(keywordFields.get("textFieldOverride"), 1);
"nestedArrayArrayField", 1.0f, assertEquals(keywordFields.get("nestedArrayStringField"), 1);
"textFieldOverride", 1.0f, assertEquals(keywordFields.get("keyPart1"), 10);
"nestedArrayStringField", 1.0f, assertEquals(keywordFields.get("esObjectField"), 1);
"keyPart1", 10.0f, assertEquals(keywordFields.get("esObjectFieldFloat"), 1);
"esObjectField", 1.0f)); assertEquals(keywordFields.get("esObjectFieldDouble"), 1);
assertEquals(keywordFields.get("esObjectFieldLong"), 1);
assertEquals(keywordFields.get("esObjectFieldInteger"), 1);
assertEquals(keywordFields.get("esObjectFieldBoolean"), 1);
SimpleQueryStringBuilder urnComponentQuery = SimpleQueryStringBuilder urnComponentQuery =
(SimpleQueryStringBuilder) analyzerGroupQuery.should().get(1); (SimpleQueryStringBuilder) analyzerGroupQuery.should().get(1);
@ -174,7 +177,7 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
assertEquals(prefixFieldWeights.size(), 29); assertEquals(prefixFieldWeights.size(), 39);
List.of( List.of(
Pair.of("urn", 100.0f), Pair.of("urn", 100.0f),
@ -209,7 +212,7 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
assertEquals(keywordQuery.queryString(), "testQuery"); assertEquals(keywordQuery.queryString(), "testQuery");
assertNull(keywordQuery.analyzer()); assertNull(keywordQuery.analyzer());
Map<String, Float> keywordFields = keywordQuery.fields(); Map<String, Float> keywordFields = keywordQuery.fields();
assertEquals(keywordFields.size(), 22); assertEquals(keywordFields.size(), 27);
assertEquals(keywordFields.get("keyPart1").floatValue(), 10.0f); assertEquals(keywordFields.get("keyPart1").floatValue(), 10.0f);
assertFalse(keywordFields.containsKey("keyPart3")); assertFalse(keywordFields.containsKey("keyPart3"));
assertEquals(keywordFields.get("textFieldOverride").floatValue(), 1.0f); assertEquals(keywordFields.get("textFieldOverride").floatValue(), 1.0f);
@ -376,7 +379,7 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
Set<SearchFieldConfig> fieldConfigs = Set<SearchFieldConfig> fieldConfigs =
TEST_CUSTOM_BUILDER.getStandardFields( TEST_CUSTOM_BUILDER.getStandardFields(
mock(EntityRegistry.class), ImmutableList.of(TestEntitySpecBuilder.getSpec())); mock(EntityRegistry.class), ImmutableList.of(TestEntitySpecBuilder.getSpec()));
assertEquals(fieldConfigs.size(), 22); assertEquals(fieldConfigs.size(), 27);
assertEquals( assertEquals(
fieldConfigs.stream().map(SearchFieldConfig::fieldName).collect(Collectors.toSet()), fieldConfigs.stream().map(SearchFieldConfig::fieldName).collect(Collectors.toSet()),
Set.of( Set.of(
@ -401,7 +404,12 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
"textFieldOverride.delimited", "textFieldOverride.delimited",
"urn", "urn",
"wordGramField.wordGrams2", "wordGramField.wordGrams2",
"customProperties.delimited")); // customProperties.delimited Saas only "customProperties.delimited",
"esObjectFieldBoolean",
"esObjectFieldInteger",
"esObjectFieldDouble",
"esObjectFieldFloat",
"esObjectFieldLong")); // customProperties.delimited Saas only
assertEquals( assertEquals(
fieldConfigs.stream() fieldConfigs.stream()
@ -487,7 +495,7 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
ImmutableList.of(TestEntitySpecBuilder.getSpec(), mockEntitySpec)); ImmutableList.of(TestEntitySpecBuilder.getSpec(), mockEntitySpec));
// Same 22 from the original entity + newFieldNotInOriginal + 3 word gram fields from the // Same 22 from the original entity + newFieldNotInOriginal + 3 word gram fields from the
// textFieldOverride // textFieldOverride
assertEquals(fieldConfigs.size(), 27); assertEquals(fieldConfigs.size(), 32);
assertEquals( assertEquals(
fieldConfigs.stream().map(SearchFieldConfig::fieldName).collect(Collectors.toSet()), fieldConfigs.stream().map(SearchFieldConfig::fieldName).collect(Collectors.toSet()),
Set.of( Set.of(
@ -517,7 +525,12 @@ public class SearchQueryBuilderTest extends AbstractTestNGSpringContextTests {
"textFieldOverride.wordGrams2", "textFieldOverride.wordGrams2",
"textFieldOverride.wordGrams3", "textFieldOverride.wordGrams3",
"textFieldOverride.wordGrams4", "textFieldOverride.wordGrams4",
"customProperties.delimited")); "customProperties.delimited",
"esObjectFieldBoolean",
"esObjectFieldInteger",
"esObjectFieldDouble",
"esObjectFieldFloat",
"esObjectFieldLong"));
// Field which only exists in first one: Should be the same // Field which only exists in first one: Should be the same
assertEquals( assertEquals(

View File

@ -228,7 +228,7 @@ public class SearchRequestHandlerTest extends AbstractTestNGSpringContextTests {
highlightBuilder.fields().stream() highlightBuilder.fields().stream()
.map(HighlightBuilder.Field::name) .map(HighlightBuilder.Field::name)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertEquals(fields.size(), 22); assertEquals(fields.size(), 32);
List<String> highlightableFields = List<String> highlightableFields =
ImmutableList.of( ImmutableList.of(
"keyPart1", "keyPart1",
@ -240,7 +240,12 @@ public class SearchRequestHandlerTest extends AbstractTestNGSpringContextTests {
"nestedArrayArrayField", "nestedArrayArrayField",
"customProperties", "customProperties",
"esObjectField", "esObjectField",
"wordGramField"); "wordGramField",
"esObjectFieldLong",
"esObjectFieldBoolean",
"esObjectFieldFloat",
"esObjectFieldDouble",
"esObjectFieldInteger");
highlightableFields.forEach( highlightableFields.forEach(
field -> { field -> {
assertTrue(fields.contains(field), "Missing: " + field); assertTrue(fields.contains(field), "Missing: " + field);

View File

@ -85,7 +85,52 @@ public class SearchDocumentTransformerTest {
assertEquals(parsedJson.get("feature2").asInt(), 1); assertEquals(parsedJson.get("feature2").asInt(), 1);
JsonNode browsePathV2 = (JsonNode) parsedJson.get("browsePathV2"); JsonNode browsePathV2 = (JsonNode) parsedJson.get("browsePathV2");
assertEquals(browsePathV2.asText(), "␟levelOne␟levelTwo"); assertEquals(browsePathV2.asText(), "␟levelOne␟levelTwo");
assertEquals(
parsedJson.get("esObjectFieldBoolean").get("key1").getNodeType(),
JsonNodeFactory.instance.booleanNode(true).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldLong").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(1L).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldFloat").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(2.0f).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldDouble").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(1.2).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldInteger").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(456).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldBoolean").get("key2").getNodeType(),
JsonNodeFactory.instance.booleanNode(false).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldLong").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(2L).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldFloat").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(1.0f).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldDouble").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(2.4).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldInteger").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(123).getNodeType());
assertEquals(parsedJson.get("esObjectField").get("key3").asText(), ""); assertEquals(parsedJson.get("esObjectField").get("key3").asText(), "");
assertEquals(
parsedJson.get("esObjectFieldBoolean").get("key2").getNodeType(),
JsonNodeFactory.instance.booleanNode(false).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldLong").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(2L).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldFloat").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(1.0f).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldDouble").get("key2").getNodeType(),
JsonNodeFactory.instance.numberNode(2.4).getNodeType());
assertEquals(
parsedJson.get("esObjectFieldInteger").get("key1").getNodeType(),
JsonNodeFactory.instance.numberNode(123).getNodeType());
} }
@Test @Test

View File

@ -103,4 +103,50 @@ record TestEntityInfo includes CustomProperties {
"fieldType": "BOOLEAN" "fieldType": "BOOLEAN"
} }
removed: optional boolean removed: optional boolean
}
@Searchable = {
"/*": {
"name": "esObjectFieldLong",
"fieldType": "OBJECT",
"queryByDefault": true
}
}
esObjectFieldLong: optional map[string, long]
@Searchable = {
"/*": {
"name": "esObjectFieldBoolean",
"fieldType": "OBJECT",
"queryByDefault": true
}
}
esObjectFieldBoolean: optional map[string, boolean]
@Searchable = {
"/*": {
"name": "esObjectFieldFloat",
"fieldType": "OBJECT",
"queryByDefault": true
}
}
esObjectFieldFloat: optional map[string, float]
@Searchable = {
"/*": {
"name": "esObjectFieldDouble",
"fieldType": "OBJECT",
"queryByDefault": true
}
}
esObjectFieldDouble: optional map[string, double]
@Searchable = {
"/*": {
"name": "esObjectFieldInteger",
"fieldType": "OBJECT",
"queryByDefault": true
}
}
esObjectFieldInteger: optional map[string, int]
}