diff --git a/core/src/test/java/org/opensearch/sql/expression/aggregation/PercentileApproxAggregatorTest.java b/core/src/test/java/org/opensearch/sql/expression/aggregation/PercentileApproxAggregatorTest.java index c0b72813d2..33fc325204 100644 --- a/core/src/test/java/org/opensearch/sql/expression/aggregation/PercentileApproxAggregatorTest.java +++ b/core/src/test/java/org/opensearch/sql/expression/aggregation/PercentileApproxAggregatorTest.java @@ -13,12 +13,18 @@ package org.opensearch.sql.expression.aggregation; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import static org.opensearch.sql.data.model.ExprValueUtils.integerValue; import static org.opensearch.sql.data.model.ExprValueUtils.longValue; -import static org.opensearch.sql.data.type.ExprCoreType.*; +import static org.opensearch.sql.data.type.ExprCoreType.DOUBLE; +import static org.opensearch.sql.data.type.ExprCoreType.FLOAT; +import static org.opensearch.sql.data.type.ExprCoreType.INTEGER; +import static org.opensearch.sql.data.type.ExprCoreType.LONG; +import static org.opensearch.sql.data.type.ExprCoreType.STRING; import java.util.ArrayList; import java.util.List; diff --git a/integ-test/src/test/java/org/opensearch/sql/legacy/JdbcTestIT.java b/integ-test/src/test/java/org/opensearch/sql/legacy/JdbcTestIT.java index 08ad78e33c..74acad4f52 100644 --- a/integ-test/src/test/java/org/opensearch/sql/legacy/JdbcTestIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/legacy/JdbcTestIT.java @@ -157,7 +157,7 @@ public void ipTypeShouldPassJdbcFormatter() { executeQuery( "SELECT host AS hostIP FROM " + TestsConstants.TEST_INDEX_WEBLOG + " ORDER BY hostIP", "jdbc"), - containsString("\"type\": \"string\"")); + containsString("\"type\": \"ip\"")); } @Test diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java index 12c8049f56..fe5c2ff270 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java @@ -50,7 +50,7 @@ public void test_nonnumeric_data_types() throws IOException { schema("binary_value", "binary"), schema("date_value", "timestamp"), schema("date_nanos_value", "timestamp"), - schema("ip_value", "string"), + schema("ip_value", "ip"), schema("object_value", "struct"), schema("nested_value", "array"), schema("geo_point_value", "geo_point")); diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/IPFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/IPFunctionIT.java index cd64b7359b..8fe14809ac 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/IPFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/IPFunctionIT.java @@ -31,27 +31,27 @@ public void test_cidrmatch() throws IOException { result = executeQuery( String.format( - "source=%s | where cidrmatch(host, '199.120.111.0/24') | fields host", + "source=%s | where cidrmatch(host_string, '199.120.111.0/24') | fields host_string", TEST_INDEX_WEBLOG)); - verifySchema(result, schema("host", null, "string")); + verifySchema(result, schema("host_string", null, "string")); verifyDataRows(result); // One match result = executeQuery( String.format( - "source=%s | where cidrmatch(host, '199.120.110.0/24') | fields host", + "source=%s | where cidrmatch(host_string, '199.120.110.0/24') | fields host_string", TEST_INDEX_WEBLOG)); - verifySchema(result, schema("host", null, "string")); + verifySchema(result, schema("host_string", null, "string")); verifyDataRows(result, rows("199.120.110.21")); // Multiple matches result = executeQuery( String.format( - "source=%s | where cidrmatch(host, '199.0.0.0/8') | fields host", + "source=%s | where cidrmatch(host_string, '199.0.0.0/8') | fields host_string", TEST_INDEX_WEBLOG)); - verifySchema(result, schema("host", null, "string")); + verifySchema(result, schema("host_string", null, "string")); verifyDataRows(result, rows("199.72.81.55"), rows("199.120.110.21")); } } diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/SystemFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/SystemFunctionIT.java index 3f92925c80..c1356ce838 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/SystemFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/SystemFunctionIT.java @@ -89,7 +89,7 @@ public void typeof_opensearch_types() throws IOException { "BOOLEAN", "OBJECT", "KEYWORD", - "KEYWORD", + "IP", "BINARY", "GEO_POINT")); } diff --git a/integ-test/src/test/resources/indexDefinitions/weblogs_index_mapping.json b/integ-test/src/test/resources/indexDefinitions/weblogs_index_mapping.json index 05b9784313..bff3e20bb9 100644 --- a/integ-test/src/test/resources/indexDefinitions/weblogs_index_mapping.json +++ b/integ-test/src/test/resources/indexDefinitions/weblogs_index_mapping.json @@ -1,9 +1,12 @@ { "mappings": { "properties": { - "host": { + "host_ip": { "type": "ip" }, + "host_string": { + "type": "keyword" + }, "method": { "type": "text" }, diff --git a/integ-test/src/test/resources/weblogs.json b/integ-test/src/test/resources/weblogs.json index 4228e9c4d2..d2e9a968f8 100644 --- a/integ-test/src/test/resources/weblogs.json +++ b/integ-test/src/test/resources/weblogs.json @@ -1,6 +1,6 @@ {"index":{}} -{"host": "199.72.81.55", "method": "GET", "url": "/history/apollo/", "response": "200", "bytes": "6245"} +{"host_ip": "199.72.81.55", "host_string": "199.72.81.55", "method": "GET", "url": "/history/apollo/", "response": "200", "bytes": "6245"} {"index":{}} -{"host": "199.120.110.21", "method": "GET", "url": "/shuttle/missions/sts-73/mission-sts-73.html", "response": "200", "bytes": "4085"} +{"host_ip": "199.120.110.21", "host_string": "199.120.110.21", "method": "GET", "url": "/shuttle/missions/sts-73/mission-sts-73.html", "response": "200", "bytes": "4085"} {"index":{}} -{"host": "205.212.115.106", "method": "GET", "url": "/shuttle/countdown/countdown.html", "response": "200", "bytes": "3985"} +{"host_ip": "205.212.115.106", "host_string": "205.212.115.106", "method": "GET", "url": "/shuttle/countdown/countdown.html", "response": "200", "bytes": "3985"} diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java index e86e604fa1..c35eacfc72 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataType.java @@ -26,7 +26,7 @@ public enum MappingType { Invalid(null, ExprCoreType.UNKNOWN), Text("text", ExprCoreType.UNKNOWN), Keyword("keyword", ExprCoreType.STRING), - Ip("ip", ExprCoreType.STRING), + Ip("ip", ExprCoreType.UNKNOWN), GeoPoint("geo_point", ExprCoreType.UNKNOWN), Binary("binary", ExprCoreType.UNKNOWN), Date("date", ExprCoreType.TIMESTAMP), diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchIpType.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchIpType.java index 9f3a521ca1..22581ec28c 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchIpType.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/type/OpenSearchIpType.java @@ -5,7 +5,7 @@ package org.opensearch.sql.opensearch.data.type; -import static org.opensearch.sql.data.type.ExprCoreType.STRING; +import static org.opensearch.sql.data.type.ExprCoreType.UNKNOWN; import lombok.EqualsAndHashCode; @@ -20,7 +20,7 @@ public class OpenSearchIpType extends OpenSearchDataType { private OpenSearchIpType() { super(MappingType.Ip); - exprCoreType = STRING; + exprCoreType = UNKNOWN; } public static OpenSearchIpType of() { diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValue.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValue.java index 3ecc39457c..30b3784bfc 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValue.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValue.java @@ -45,9 +45,4 @@ public boolean equal(ExprValue other) { public int hashCode() { return Objects.hashCode(ip); } - - @Override - public String stringValue() { - return ip; - } } diff --git a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java index ab63663f87..da51c34cec 100644 --- a/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java +++ b/opensearch/src/main/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactory.java @@ -71,8 +71,32 @@ /** Construct ExprValue from OpenSearch response. */ public class OpenSearchExprValueFactory { + /** The Mapping of Field and ExprType. */ + private final Map typeMapping; + + /** Whether to support nested value types (such as arrays) */ + private final boolean fieldTypeTolerance; + + /** + * Extend existing mapping by new data without overwrite. Called from aggregation only {@see + * AggregationQueryBuilder#buildTypeMapping}. + * + * @param typeMapping A data type mapping produced by aggregation. + */ + public void extendTypeMapping(Map typeMapping) { + for (var field : typeMapping.keySet()) { + // Prevent overwriting, because aggregation engine may be not aware + // of all niceties of all types. + this.typeMapping.putIfAbsent(field, typeMapping.get(field)); + } + } + + @Getter @Setter private OpenSearchAggregationResponseParser parser; + private static final String TOP_PATH = ""; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final Map> typeActionMap = new ImmutableMap.Builder>() .put( @@ -110,20 +134,12 @@ public class OpenSearchExprValueFactory { OpenSearchExprValueFactory::createOpenSearchDateType) .put( OpenSearchDataType.of(OpenSearchDataType.MappingType.Ip), - (c, dt) -> new ExprStringValue(c.stringValue())) + (c, dt) -> new OpenSearchExprIpValue(c.stringValue())) .put( OpenSearchDataType.of(OpenSearchDataType.MappingType.Binary), (c, dt) -> new OpenSearchExprBinaryValue(c.stringValue())) .build(); - /** The Mapping of Field and ExprType. */ - private final Map typeMapping; - - /** Whether to support nested value types (such as arrays) */ - private final boolean fieldTypeTolerance; - - @Getter @Setter private OpenSearchAggregationResponseParser parser; - /** Constructor of OpenSearchExprValueFactory. */ public OpenSearchExprValueFactory( Map typeMapping, boolean fieldTypeTolerance) { @@ -131,6 +147,80 @@ public OpenSearchExprValueFactory( this.fieldTypeTolerance = fieldTypeTolerance; } + /** + * + * + *
+   * The struct construction has the following assumption:
+   *  1. The field has OpenSearch Object data type.
+   *     See 
+   *       docs
+   *  2. The deeper field is flattened in the typeMapping. e.g.
+   *     { "employ",       "STRUCT"  }
+   *     { "employ.id",    "INTEGER" }
+   *     { "employ.state", "STRING"  }
+   *  
+ */ + public ExprValue construct(String jsonString, boolean supportArrays) { + try { + return parse( + new OpenSearchJsonContent(OBJECT_MAPPER.readTree(jsonString)), + TOP_PATH, + Optional.of(STRUCT), + fieldTypeTolerance || supportArrays); + } catch (JsonProcessingException e) { + throw new IllegalStateException(String.format("invalid json: %s.", jsonString), e); + } + } + + /** + * Construct ExprValue from field and its value object. Throw exception if trying to construct + * from field of unsupported type.
+ * Todo, add IP, GeoPoint support after we have function implementation around it. + * + * @param field field name + * @param value value object + * @return ExprValue + */ + public ExprValue construct(String field, Object value, boolean supportArrays) { + return parse(new ObjectContent(value), field, type(field), supportArrays); + } + + private ExprValue parse( + Content content, String field, Optional fieldType, boolean supportArrays) { + if (content.isNull() || !fieldType.isPresent()) { + return ExprNullValue.of(); + } + + final ExprType type = fieldType.get(); + + if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.GeoPoint))) { + return parseGeoPoint(content, supportArrays); + } else if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.Nested)) + || content.isArray()) { + return parseArray(content, field, type, supportArrays); + } else if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.Object)) + || type == STRUCT) { + return parseStruct(content, field, supportArrays); + } else { + if (typeActionMap.containsKey(type)) { + return typeActionMap.get(type).apply(content, type); + } else { + throw new IllegalStateException( + String.format( + "Unsupported type: %s for value: %s.", type.typeName(), content.objectValue())); + } + } + } + + /** + * In OpenSearch, it is possible field doesn't have type definition in mapping. but has empty + * value. For example, {"empty_field": []}. + */ + private Optional type(String field) { + return Optional.ofNullable(typeMapping.get(field)); + } + /** * Parse value with the first matching formatter into {@link ExprValue} with corresponding {@link * ExprCoreType}. @@ -218,92 +308,6 @@ private static ExprValue createOpenSearchDateType(Content value, ExprType type) return new ExprTimestampValue((Instant) value.objectValue()); } - /** - * Extend existing mapping by new data without overwrite. Called from aggregation only {@see - * AggregationQueryBuilder#buildTypeMapping}. - * - * @param typeMapping A data type mapping produced by aggregation. - */ - public void extendTypeMapping(Map typeMapping) { - for (var field : typeMapping.keySet()) { - // Prevent overwriting, because aggregation engine may be not aware - // of all niceties of all types. - this.typeMapping.putIfAbsent(field, typeMapping.get(field)); - } - } - - /** - * - * - *
-   * The struct construction has the following assumption:
-   *  1. The field has OpenSearch Object data type.
-   *     See 
-   *       docs
-   *  2. The deeper field is flattened in the typeMapping. e.g.
-   *     { "employ",       "STRUCT"  }
-   *     { "employ.id",    "INTEGER" }
-   *     { "employ.state", "STRING"  }
-   *  
- */ - public ExprValue construct(String jsonString, boolean supportArrays) { - try { - return parse( - new OpenSearchJsonContent(OBJECT_MAPPER.readTree(jsonString)), - TOP_PATH, - Optional.of(STRUCT), - fieldTypeTolerance || supportArrays); - } catch (JsonProcessingException e) { - throw new IllegalStateException(String.format("invalid json: %s.", jsonString), e); - } - } - - /** - * Construct ExprValue from field and its value object. Throw exception if trying to construct - * from field of unsupported type.
- * Todo, add IP, GeoPoint support after we have function implementation around it. - * - * @param field field name - * @param value value object - * @return ExprValue - */ - public ExprValue construct(String field, Object value, boolean supportArrays) { - return parse(new ObjectContent(value), field, type(field), supportArrays); - } - - private ExprValue parse( - Content content, String field, Optional fieldType, boolean supportArrays) { - if (content.isNull() || !fieldType.isPresent()) { - return ExprNullValue.of(); - } - - final ExprType type = fieldType.get(); - - if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.GeoPoint))) { - return parseGeoPoint(content, supportArrays); - } else if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.Nested)) - || content.isArray()) { - return parseArray(content, field, type, supportArrays); - } else if (type.equals(OpenSearchDataType.of(OpenSearchDataType.MappingType.Object)) - || type == STRUCT) { - return parseStruct(content, field, supportArrays); - } else if (typeActionMap.containsKey(type)) { - return typeActionMap.get(type).apply(content, type); - } else { - throw new IllegalStateException( - String.format( - "Unsupported type: %s for value: %s.", type.typeName(), content.objectValue())); - } - } - - /** - * In OpenSearch, it is possible field doesn't have type definition in mapping. but has empty - * value. For example, {"empty_field": []}. - */ - private Optional type(String field) { - return Optional.ofNullable(typeMapping.get(field)); - } - /** * Parse struct content. * diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataTypeTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataTypeTest.java index abbd524441..76fbbd6e65 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataTypeTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/type/OpenSearchDataTypeTest.java @@ -54,28 +54,6 @@ class OpenSearchDataTypeTest { private static final OpenSearchDateType dateType = OpenSearchDateType.of(emptyFormatString); - private static Stream getTestDataWithType() { - return Stream.of( - Arguments.of(MappingType.Text, "text", OpenSearchTextType.of()), - Arguments.of(MappingType.Keyword, "keyword", STRING), - Arguments.of(MappingType.Byte, "byte", BYTE), - Arguments.of(MappingType.Short, "short", SHORT), - Arguments.of(MappingType.Integer, "integer", INTEGER), - Arguments.of(MappingType.Long, "long", LONG), - Arguments.of(MappingType.HalfFloat, "half_float", FLOAT), - Arguments.of(MappingType.Float, "float", FLOAT), - Arguments.of(MappingType.ScaledFloat, "scaled_float", DOUBLE), - Arguments.of(MappingType.Double, "double", DOUBLE), - Arguments.of(MappingType.Boolean, "boolean", BOOLEAN), - Arguments.of(MappingType.Date, "timestamp", TIMESTAMP), - Arguments.of(MappingType.DateNanos, "timestamp", TIMESTAMP), - Arguments.of(MappingType.Object, "object", STRUCT), - Arguments.of(MappingType.Nested, "nested", ARRAY), - Arguments.of(MappingType.GeoPoint, "geo_point", OpenSearchGeoPointType.of()), - Arguments.of(MappingType.Binary, "binary", OpenSearchBinaryType.of()), - Arguments.of(MappingType.Ip, "ip", STRING)); - } - @Test public void isCompatible() { assertTrue(STRING.isCompatible(textType)); @@ -113,6 +91,28 @@ public void shouldCast() { assertFalse(textKeywordType.shouldCast(STRING)); } + private static Stream getTestDataWithType() { + return Stream.of( + Arguments.of(MappingType.Text, "text", OpenSearchTextType.of()), + Arguments.of(MappingType.Keyword, "keyword", STRING), + Arguments.of(MappingType.Byte, "byte", BYTE), + Arguments.of(MappingType.Short, "short", SHORT), + Arguments.of(MappingType.Integer, "integer", INTEGER), + Arguments.of(MappingType.Long, "long", LONG), + Arguments.of(MappingType.HalfFloat, "half_float", FLOAT), + Arguments.of(MappingType.Float, "float", FLOAT), + Arguments.of(MappingType.ScaledFloat, "scaled_float", DOUBLE), + Arguments.of(MappingType.Double, "double", DOUBLE), + Arguments.of(MappingType.Boolean, "boolean", BOOLEAN), + Arguments.of(MappingType.Date, "timestamp", TIMESTAMP), + Arguments.of(MappingType.DateNanos, "timestamp", TIMESTAMP), + Arguments.of(MappingType.Object, "object", STRUCT), + Arguments.of(MappingType.Nested, "nested", ARRAY), + Arguments.of(MappingType.GeoPoint, "geo_point", OpenSearchGeoPointType.of()), + Arguments.of(MappingType.Binary, "binary", OpenSearchBinaryType.of()), + Arguments.of(MappingType.Ip, "ip", OpenSearchIpType.of())); + } + @ParameterizedTest(name = "{1}") @MethodSource("getTestDataWithType") public void of_MappingType(MappingType mappingType, String name, ExprType dataType) { diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValueTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValueTest.java index 8483d82b20..5ee175f304 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValueTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprIpValueTest.java @@ -42,9 +42,4 @@ void testEqual() { void testHashCode() { assertNotNull(ipValue.hashCode()); } - - @Test - void testStringValue() { - assertEquals(ipString, ipValue.stringValue()); - } } diff --git a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactoryTest.java b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactoryTest.java index 91c0539d4b..d82926077e 100644 --- a/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactoryTest.java +++ b/opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactoryTest.java @@ -51,7 +51,6 @@ import org.opensearch.geometry.utils.Geohash; import org.opensearch.sql.data.model.ExprCollectionValue; import org.opensearch.sql.data.model.ExprDateValue; -import org.opensearch.sql.data.model.ExprStringValue; import org.opensearch.sql.data.model.ExprTimeValue; import org.opensearch.sql.data.model.ExprTimestampValue; import org.opensearch.sql.data.model.ExprTupleValue; @@ -666,7 +665,8 @@ public void constructArrayOfIPsReturnsAll() { final String ip2 = "192.168.0.2"; assertEquals( - new ExprCollectionValue(List.of(new ExprStringValue(ip1), new ExprStringValue(ip2))), + new ExprCollectionValue( + List.of(new OpenSearchExprIpValue(ip1), new OpenSearchExprIpValue(ip2))), tupleValue(String.format("{\"%s\":[\"%s\",\"%s\"]}", fieldIp, ip1, ip2)).get(fieldIp)); } @@ -743,17 +743,10 @@ public void constructStruct() { @Test public void constructIP() { - final String valueIpv4 = "192.168.0.1"; - assertEquals( - stringValue(valueIpv4), - tupleValue(String.format("{\"%s\":\"%s\"}", fieldIp, valueIpv4)).get(fieldIp)); - assertEquals(stringValue(valueIpv4), constructFromObject(fieldIp, valueIpv4)); - - final String valueIpv6 = "2001:0db7::ff00:42:8329"; + final String valueIp = "192.168.0.1"; assertEquals( - stringValue(valueIpv6), - tupleValue(String.format("{\"%s\":\"%s\"}", fieldIp, valueIpv6)).get(fieldIp)); - assertEquals(stringValue(valueIpv6), constructFromObject(fieldIp, valueIpv6)); + new OpenSearchExprIpValue(valueIp), + tupleValue(String.format("{\"%s\":\"%s\"}", fieldIp, valueIp)).get(fieldIp)); } @Test