From fe5d539247e2c8273d2e5b9b8e41707d13c8a494 Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Tue, 1 Sep 2020 08:38:21 -0700 Subject: [PATCH] support Elasticsearch geo_point and ip data type (#719) --- docs/experiment/ppl/cmd/stats.rst | 4 +- docs/experiment/ppl/interfaces/endpoint.rst | 2 +- docs/experiment/ppl/interfaces/protocol.rst | 4 +- docs/user/dql/functions.rst | 16 +-- docs/user/general/datatypes.rst | 10 ++ .../data/type/ElasticsearchDataType.java | 8 +- .../value/ElasticsearchExprGeoPointValue.java | 73 ++++++++++++ .../data/value/ElasticsearchExprIpValue.java | 61 ++++++++++ .../value/ElasticsearchExprValueFactory.java | 12 +- .../storage/ElasticsearchIndex.java | 4 + .../ElasticsearchExprGeoPointValueTest.java | 57 ++++++++++ .../value/ElasticsearchExprIpValueTest.java | 55 +++++++++ .../ElasticsearchExprValueFactoryTest.java | 16 +++ .../sql/ppl/FieldsCommandIT.java | 44 ++++++-- .../sql/ppl/MathematicalFunctionIT.java | 8 +- .../sql/ppl/OperatorIT.java | 26 +---- .../sql/ppl/PPLPluginIT.java | 17 +-- .../sql/ppl/StandaloneIT.java | 20 ++-- .../sql/ppl/StatsCommandIT.java | 105 +++++------------- .../sql/sql/IdentifierIT.java | 26 +++-- .../sql/sql/MathematicalFunctionIT.java | 6 +- protocol/build.gradle | 4 +- .../format/JsonResponseFormatter.java | 15 ++- .../SimpleJsonResponseFormatterTest.java | 16 +-- 24 files changed, 434 insertions(+), 175 deletions(-) create mode 100644 elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValue.java create mode 100644 elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValue.java create mode 100644 elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValueTest.java create mode 100644 elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValueTest.java diff --git a/docs/experiment/ppl/cmd/stats.rst b/docs/experiment/ppl/cmd/stats.rst index 5d0ce101c6..ed4f45aada 100644 --- a/docs/experiment/ppl/cmd/stats.rst +++ b/docs/experiment/ppl/cmd/stats.rst @@ -51,7 +51,7 @@ PPL query:: +----------+--------------------+ | gender | avg(age) | |----------+--------------------| - | F | 28 | + | F | 28.0 | | M | 33.666666666666664 | +----------+--------------------+ @@ -68,7 +68,7 @@ PPL query:: +----------+--------------------+------------+ | gender | avg(age) | sum(age) | |----------+--------------------+------------| - | F | 28 | 28 | + | F | 28.0 | 28 | | M | 33.666666666666664 | 101 | +----------+--------------------+------------+ diff --git a/docs/experiment/ppl/interfaces/endpoint.rst b/docs/experiment/ppl/interfaces/endpoint.rst index 45e34b28b1..dd4df5e0ad 100644 --- a/docs/experiment/ppl/interfaces/endpoint.rst +++ b/docs/experiment/ppl/interfaces/endpoint.rst @@ -43,7 +43,6 @@ PPL query:: "type": "string" } ], - "total": 4, "datarows": [ [ "Amber", @@ -62,6 +61,7 @@ PPL query:: "Adams" ] ], + "total": 4, "size": 4 } diff --git a/docs/experiment/ppl/interfaces/protocol.rst b/docs/experiment/ppl/interfaces/protocol.rst index 0623823c57..8cb4ea7b5b 100644 --- a/docs/experiment/ppl/interfaces/protocol.rst +++ b/docs/experiment/ppl/interfaces/protocol.rst @@ -43,7 +43,6 @@ PPL query:: "type": "string" } ], - "total": 4, "datarows": [ [ "Amber", @@ -62,6 +61,7 @@ PPL query:: "Adams" ] ], + "total": 4, "size": 4 } @@ -94,7 +94,6 @@ PPL query:: "type": "string" } ], - "total": 4, "datarows": [ [ "Amber", @@ -113,6 +112,7 @@ PPL query:: "Adams" ] ], + "total": 4, "size": 4 } diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 85ed109425..e7edf3e7c2 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -91,7 +91,7 @@ Example:: +-----------+ | ASIN(0) | |-----------| - | 0 | + | 0.0 | +-----------+ @@ -229,7 +229,7 @@ Example:: +----------+ | COS(0) | |----------| - | 1 | + | 1.0 | +----------+ @@ -676,7 +676,7 @@ Example:: +-------------+--------------+--------------------+ | POW(3, 2) | POW(-3, 2) | POW(3, -2) | |-------------+--------------+--------------------| - | 9 | 9 | 0.1111111111111111 | + | 9.0 | 9.0 | 0.1111111111111111 | +-------------+--------------+--------------------+ @@ -699,7 +699,7 @@ Example:: +---------------+----------------+--------------------+ | POWER(3, 2) | POWER(-3, 2) | POWER(3, -2) | |---------------+----------------+--------------------| - | 9 | 9 | 0.1111111111111111 | + | 9.0 | 9.0 | 0.1111111111111111 | +---------------+----------------+--------------------+ @@ -804,7 +804,7 @@ Example:: +----------------+-------------------+--------------------+----------------+ | ROUND(12.34) | ROUND(12.34, 1) | ROUND(12.34, -1) | ROUND(12, 1) | |----------------+-------------------+--------------------+----------------| - | 12 | 12.3 | 10 | 12 | + | 12.0 | 12.3 | 10.0 | 12 | +----------------+-------------------+--------------------+----------------+ @@ -872,7 +872,7 @@ Example:: +----------+ | SIN(0) | |----------| - | 0 | + | 0.0 | +----------+ @@ -909,7 +909,7 @@ Example:: +-----------+--------------+ | SQRT(4) | SQRT(4.41) | |-----------+--------------| - | 2 | 2.1 | + | 2.0 | 2.1 | +-----------+--------------+ @@ -954,7 +954,7 @@ Example:: +----------+ | TAN(0) | |----------| - | 0 | + | 0.0 | +----------+ diff --git a/docs/user/general/datatypes.rst b/docs/user/general/datatypes.rst index 321f03a71f..70debe4e93 100644 --- a/docs/user/general/datatypes.rst +++ b/docs/user/general/datatypes.rst @@ -45,6 +45,10 @@ The ODFE SQL Engine support the following data types. +---------------+ | interval | +---------------+ +| ip | ++---------------+ +| geo_point | ++---------------+ | struct | +---------------+ | array | @@ -66,6 +70,8 @@ The table below list the mapping between Elasticsearch Data Type, ODFE SQL Data +--------------------+---------------+-----------+ | float | float | FLOAT | +--------------------+---------------+-----------+ +| half_float | float | FLOAT | ++--------------------+---------------+-----------+ | double | double | DOUBLE | +--------------------+---------------+-----------+ | keyword | string | VARCHAR | @@ -74,6 +80,10 @@ The table below list the mapping between Elasticsearch Data Type, ODFE SQL Data +--------------------+---------------+-----------+ | date | timestamp | TIMESTAMP | +--------------------+---------------+-----------+ +| ip | ip | IP | ++--------------------+---------------+-----------+ +| date | timestamp | TIMESTAMP | ++--------------------+---------------+-----------+ | object | struct | STRUCT | +--------------------+---------------+-----------+ | nested | array | TBD | diff --git a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/type/ElasticsearchDataType.java b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/type/ElasticsearchDataType.java index cb2a277356..761b937ab6 100644 --- a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/type/ElasticsearchDataType.java +++ b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/type/ElasticsearchDataType.java @@ -18,6 +18,7 @@ package com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRING; +import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.UNKNOWN; import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; import java.util.Arrays; @@ -40,7 +41,12 @@ public enum ElasticsearchDataType implements ExprType { * Elasticsearch multi-fields which has text and keyword. * Ref: https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html */ - ES_TEXT_KEYWORD(Arrays.asList(STRING, ES_TEXT), "string"); + ES_TEXT_KEYWORD(Arrays.asList(STRING, ES_TEXT), "string"), + + + ES_IP(Arrays.asList(UNKNOWN), "ip"), + + ES_GEO_POINT(Arrays.asList(UNKNOWN), "geo_point"); /** * Parent of current type. diff --git a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValue.java b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValue.java new file mode 100644 index 0000000000..2dcd1b72a8 --- /dev/null +++ b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValue.java @@ -0,0 +1,73 @@ +/* + * + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value; + +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_GEO_POINT; + +import com.amazon.opendistroforelasticsearch.sql.data.model.AbstractExprValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; +import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; +import java.util.Objects; +import lombok.Data; + +/** + * Elasticsearch GeoPointValue. + * Todo, add this to avoid the unknown value type exception, the implementation will be changed. + */ +public class ElasticsearchExprGeoPointValue extends AbstractExprValue { + + private final GeoPoint geoPoint; + + public ElasticsearchExprGeoPointValue(Double lat, Double lon) { + this.geoPoint = new GeoPoint(lat, lon); + } + + @Override + public Object value() { + return geoPoint; + } + + @Override + public ExprType type() { + return ES_GEO_POINT; + } + + @Override + public int compare(ExprValue other) { + return geoPoint.toString() + .compareTo((((ElasticsearchExprGeoPointValue) other).geoPoint).toString()); + } + + @Override + public boolean equal(ExprValue other) { + return geoPoint.equals(((ElasticsearchExprGeoPointValue) other).geoPoint); + } + + @Override + public int hashCode() { + return Objects.hashCode(geoPoint); + } + + @Data + public static class GeoPoint { + + private final Double lat; + + private final Double lon; + } +} diff --git a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValue.java b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValue.java new file mode 100644 index 0000000000..e1d026f5e0 --- /dev/null +++ b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValue.java @@ -0,0 +1,61 @@ +/* + * + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value; + +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_IP; + +import com.amazon.opendistroforelasticsearch.sql.data.model.AbstractExprValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; +import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; +import java.util.Objects; +import lombok.RequiredArgsConstructor; + +/** + * Elasticsearch IP ExprValue. + * Todo, add this to avoid the unknown value type exception, the implementation will be changed. + */ +@RequiredArgsConstructor +public class ElasticsearchExprIpValue extends AbstractExprValue { + + private final String ip; + + @Override + public Object value() { + return ip; + } + + @Override + public ExprType type() { + return ES_IP; + } + + @Override + public int compare(ExprValue other) { + return ip.compareTo(((ElasticsearchExprIpValue) other).ip); + } + + @Override + public boolean equal(ExprValue other) { + return ip.equals(((ElasticsearchExprIpValue) other).ip); + } + + @Override + public int hashCode() { + return Objects.hashCode(ip); + } +} diff --git a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactory.java b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactory.java index db05161ef9..5b2fe8c07f 100644 --- a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactory.java +++ b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactory.java @@ -27,6 +27,8 @@ import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRING; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRUCT; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP; +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_GEO_POINT; +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_IP; import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_TEXT; import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_TEXT_KEYWORD; import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value.ElasticsearchDateFormatters.SQL_LITERAL_DATE_TIME_FORMAT; @@ -120,6 +122,11 @@ private ExprValue construct(String field, JsonNode value) { return new ElasticsearchExprTextValue(value.asText()); } else if (type.equals(ES_TEXT_KEYWORD)) { return new ElasticsearchExprTextKeywordValue(value.asText()); + } else if (type.equals(ES_IP)) { + return new ElasticsearchExprIpValue(value.asText()); + } else if (type.equals(ES_GEO_POINT)) { + return new ElasticsearchExprGeoPointValue(value.get("lat").doubleValue(), + value.get("lon").doubleValue()); } else { throw new IllegalStateException( String.format( @@ -130,6 +137,7 @@ private ExprValue construct(String field, JsonNode value) { /** * 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 @@ -167,8 +175,8 @@ public ExprValue construct(String field, Object value) { return new ElasticsearchExprTextKeywordValue((String) value); } else { throw new IllegalStateException(String.format( - "Unsupported type %s to construct expression value from object for " - + "field: %s, value: %s.", type.typeName(), field, value)); + "Unsupported type %s to construct expression value from object for " + + "field: %s, value: %s.", type.typeName(), field, value)); } } diff --git a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchIndex.java b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchIndex.java index 19beeae465..ab06267c46 100644 --- a/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchIndex.java +++ b/elasticsearch/src/main/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/storage/ElasticsearchIndex.java @@ -21,6 +21,7 @@ import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType; import com.amazon.opendistroforelasticsearch.sql.elasticsearch.client.ElasticsearchClient; import com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType; +import com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value.ElasticsearchExprIpValue; import com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value.ElasticsearchExprValueFactory; import com.amazon.opendistroforelasticsearch.sql.elasticsearch.mapping.IndexMapping; import com.amazon.opendistroforelasticsearch.sql.elasticsearch.storage.script.filter.FilterQueryBuilder; @@ -53,11 +54,14 @@ public class ElasticsearchIndex implements Table { .put("integer", ExprCoreType.INTEGER) .put("long", ExprCoreType.LONG) .put("float", ExprCoreType.FLOAT) + .put("half_float", ExprCoreType.FLOAT) .put("double", ExprCoreType.DOUBLE) .put("boolean", ExprCoreType.BOOLEAN) .put("nested", ExprCoreType.ARRAY) .put("object", ExprCoreType.STRUCT) .put("date", ExprCoreType.TIMESTAMP) + .put("ip", ElasticsearchDataType.ES_IP) + .put("geo_point", ElasticsearchDataType.ES_GEO_POINT) .build(); /** Elasticsearch client connection. */ diff --git a/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValueTest.java b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValueTest.java new file mode 100644 index 0000000000..e63bfe6434 --- /dev/null +++ b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprGeoPointValueTest.java @@ -0,0 +1,57 @@ +/* + * + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value; + +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_GEO_POINT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ElasticsearchExprGeoPointValueTest { + + private ElasticsearchExprGeoPointValue geoPointValue = new ElasticsearchExprGeoPointValue(1.0, + 1.0); + + @Test + void value() { + assertEquals(new ElasticsearchExprGeoPointValue.GeoPoint(1.0, 1.0), geoPointValue.value()); + } + + @Test + void type() { + assertEquals(ES_GEO_POINT, geoPointValue.type()); + } + + @Test + void compare() { + assertEquals(0, geoPointValue.compareTo(new ElasticsearchExprGeoPointValue(1.0, 1.0))); + } + + @Test + void equal() { + assertTrue(geoPointValue.equal(new ElasticsearchExprGeoPointValue(1.0, + 1.0))); + } + + @Test + void testHashCode() { + assertNotNull(geoPointValue.hashCode()); + } +} \ No newline at end of file diff --git a/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValueTest.java b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValueTest.java new file mode 100644 index 0000000000..256de743a5 --- /dev/null +++ b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprIpValueTest.java @@ -0,0 +1,55 @@ +/* + * + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.value; + +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_IP; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class ElasticsearchExprIpValueTest { + + private ElasticsearchExprIpValue ipValue = new ElasticsearchExprIpValue("192.168.0.1"); + + @Test + void value() { + assertEquals("192.168.0.1", ipValue.value()); + } + + @Test + void type() { + assertEquals(ES_IP, ipValue.type()); + } + + @Test + void compare() { + assertEquals(0, ipValue.compareTo(new ElasticsearchExprIpValue("192.168.0.1"))); + } + + @Test + void equal() { + assertTrue(ipValue.equal(new ElasticsearchExprIpValue("192.168.0.1"))); + } + + @Test + void testHashCode() { + assertNotNull(ipValue.hashCode()); + } +} diff --git a/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactoryTest.java b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactoryTest.java index 23d7427361..c668a8d897 100644 --- a/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactoryTest.java +++ b/elasticsearch/src/test/java/com/amazon/opendistroforelasticsearch/sql/elasticsearch/data/value/ElasticsearchExprValueFactoryTest.java @@ -33,6 +33,8 @@ import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRING; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.STRUCT; import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP; +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_GEO_POINT; +import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_IP; import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_TEXT; import static com.amazon.opendistroforelasticsearch.sql.elasticsearch.data.type.ElasticsearchDataType.ES_TEXT_KEYWORD; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -71,6 +73,8 @@ class ElasticsearchExprValueFactoryTest { .put("arrayV.author", STRING) .put("textV", ES_TEXT) .put("textKeywordV", ES_TEXT_KEYWORD) + .put("ipV", ES_IP) + .put("geoV", ES_GEO_POINT) .build(); private ElasticsearchExprValueFactory exprValueFactory = new ElasticsearchExprValueFactory(MAPPING); @@ -196,6 +200,18 @@ public void constructStruct() { tupleValue("{\"structV\":{\"id\":1,\"state\":\"WA\"}}").get("structV")); } + @Test + public void constructIP() { + assertEquals(new ElasticsearchExprIpValue("192.168.0.1"), + tupleValue("{\"ipV\":\"192.168.0.1\"}").get("ipV")); + } + + @Test + public void constructGeoPoint() { + assertEquals(new ElasticsearchExprGeoPointValue(42.60355556, -97.25263889), + tupleValue("{\"geoV\":{\"lat\":42.60355556,\"lon\":-97.25263889}}").get("geoV")); + } + @Test public void constructFromInvalidJsonThrowException() { IllegalStateException exception = diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/FieldsCommandIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/FieldsCommandIT.java index f44bfbd19f..f06b8524da 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/FieldsCommandIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/FieldsCommandIT.java @@ -25,7 +25,11 @@ import static com.amazon.opendistroforelasticsearch.sql.legacy.TestsConstants.TEST_INDEX_BANK; import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.columnName; import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.columnPattern; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.rows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.schema; import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifyColumn; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifyDataRows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifySchema; public class FieldsCommandIT extends PPLIntegTestCase { @@ -64,20 +68,36 @@ public void testSelectDateTypeField() throws IOException { String.format("source=%s | fields birthdate", TEST_INDEX_BANK)); assertEquals( "{\n" - + " \"schema\": [{\n" - + " \"name\": \"birthdate\",\n" - + " \"type\": \"timestamp\"\n" - + " }],\n" - + " \"total\": 7,\n" + + " \"schema\": [\n" + + " {\n" + + " \"name\": \"birthdate\",\n" + + " \"type\": \"timestamp\"\n" + + " }\n" + + " ],\n" + " \"datarows\": [\n" - + " [\"2017-10-23 00:00:00\"],\n" - + " [\"2017-11-20 00:00:00\"],\n" - + " [\"2018-06-23 00:00:00\"],\n" - + " [\"2018-11-13 23:33:20\"],\n" - + " [\"2018-06-27 00:00:00\"],\n" - + " [\"2018-08-19 00:00:00\"],\n" - + " [\"2018-08-11 00:00:00\"]\n" + + " [\n" + + " \"2017-10-23 00:00:00\"\n" + + " ],\n" + + " [\n" + + " \"2017-11-20 00:00:00\"\n" + + " ],\n" + + " [\n" + + " \"2018-06-23 00:00:00\"\n" + + " ],\n" + + " [\n" + + " \"2018-11-13 23:33:20\"\n" + + " ],\n" + + " [\n" + + " \"2018-06-27 00:00:00\"\n" + + " ],\n" + + " [\n" + + " \"2018-08-19 00:00:00\"\n" + + " ],\n" + + " [\n" + + " \"2018-08-11 00:00:00\"\n" + + " ]\n" + " ],\n" + + " \"total\": 7,\n" + " \"size\": 7\n" + "}\n", result); diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/MathematicalFunctionIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/MathematicalFunctionIT.java index dfd1b1df42..2a214ee116 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/MathematicalFunctionIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/MathematicalFunctionIT.java @@ -216,7 +216,7 @@ public void testPow() throws IOException { "source=%s | eval f = pow(age, 2) | fields f", TEST_INDEX_BANK)); verifySchema(pow, schema("f", null, "double")); verifyDataRows( - pow, rows(1024), rows(1296), rows(784), rows(1089), rows(1296), rows(1521), rows(1156)); + pow, rows(1024.0), rows(1296.0), rows(784.0), rows(1089.0), rows(1296.0), rows(1521.0), rows(1156.0)); JSONObject power = executeQuery( @@ -224,7 +224,7 @@ public void testPow() throws IOException { "source=%s | eval f = power(age, 2) | fields f", TEST_INDEX_BANK)); verifySchema(power, schema("f", null, "double")); verifyDataRows( - power, rows(1024), rows(1296), rows(784), rows(1089), rows(1296), rows(1521), rows(1156)); + power, rows(1024.0), rows(1296.0), rows(784.0), rows(1089.0), rows(1296.0), rows(1521.0), rows(1156.0)); } @@ -266,8 +266,8 @@ public void testSqrt() throws IOException { "source=%s | eval f = sqrt(age) | fields f", TEST_INDEX_BANK)); verifySchema(result, schema("f", null, "double")); verifyDataRows(result, - rows(5.656854249492381), rows(6), rows(5.291502622129181), - rows(5.744562646538029), rows(6), rows(6.244997998398398), + rows(5.656854249492381), rows(6.0), rows(5.291502622129181), + rows(5.744562646538029), rows(6.0), rows(6.244997998398398), rows(5.830951894845301)); } diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/OperatorIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/OperatorIT.java index d8a74e1025..d608abfdca 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/OperatorIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/OperatorIT.java @@ -84,30 +84,14 @@ public void testModuleOperator() throws IOException { @Test public void testArithmeticOperatorWithNullValue() throws IOException { - String result = - executeQueryToString( + JSONObject result = + executeQuery( String.format( "source=%s | eval f = age + 0 | fields f", TEST_INDEX_BANK_WITH_NULL_VALUES)); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"f\",\n" - + " \"type\": \"integer\"\n" - + " }],\n" - + " \"total\": 7,\n" - + " \"datarows\": [\n" - + " [32],\n" - + " [36],\n" - + " [28],\n" - + " [33],\n" - + " [36],\n" - + " [null],\n" - + " [34]\n" - + " ],\n" - + " \"size\": 7\n" - + "}\n", - result); + verifyDataRows( + result, rows(32), rows(36), rows(28), rows(33), rows(36), rows(JSONObject.NULL), + rows(34)); } @Test diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLPluginIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLPluginIT.java index b792c31bfb..d84bbb145d 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLPluginIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLPluginIT.java @@ -17,7 +17,9 @@ import static com.amazon.opendistroforelasticsearch.sql.legacy.TestsConstants.TEST_INDEX_BANK; import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.rows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.schema; import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifyDataRows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifySchema; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasProperty; @@ -51,18 +53,9 @@ public void testQueryEndpointShouldOK() throws IOException { request.setJsonEntity("{\"name\": \"hello\"}"); client().performRequest(request); - String response = executeQueryToString("search source=a"); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"name\",\n" - + " \"type\": \"string\"\n" - + " }],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[\"hello\"]],\n" - + " \"size\": 1\n" - + "}\n", - response); + JSONObject response = executeQuery("search source=a"); + verifySchema(response, schema("name", null, "string")); + verifyDataRows(response, rows("hello")); } @Test diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StandaloneIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StandaloneIT.java index a3e175fc4f..e76c0da6a1 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StandaloneIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StandaloneIT.java @@ -91,15 +91,21 @@ public void testSourceFieldQuery() throws IOException { String actual = executeByStandaloneQueryEngine("source=test | fields name"); assertEquals( "{\n" - + " \"schema\": [{\n" - + " \"name\": \"name\",\n" - + " \"type\": \"string\"\n" - + " }],\n" - + " \"total\": 2,\n" + + " \"schema\": [\n" + + " {\n" + + " \"name\": \"name\",\n" + + " \"type\": \"string\"\n" + + " }\n" + + " ],\n" + " \"datarows\": [\n" - + " [\"hello\"],\n" - + " [\"world\"]\n" + + " [\n" + + " \"hello\"\n" + + " ],\n" + + " [\n" + + " \"world\"\n" + + " ]\n" + " ],\n" + + " \"total\": 2,\n" + " \"size\": 2\n" + "}", actual); diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StatsCommandIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StatsCommandIT.java index f9474ab11f..74808a1436 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StatsCommandIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/StatsCommandIT.java @@ -16,9 +16,13 @@ package com.amazon.opendistroforelasticsearch.sql.ppl; import static com.amazon.opendistroforelasticsearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.rows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.schema; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifyDataRows; +import static com.amazon.opendistroforelasticsearch.sql.util.MatcherUtils.verifySchema; -import com.amazon.opendistroforelasticsearch.sql.legacy.SQLIntegTestCase; import java.io.IOException; +import org.json.JSONObject; import org.junit.jupiter.api.Test; public class StatsCommandIT extends PPLIntegTestCase { @@ -31,101 +35,46 @@ public void init() throws IOException { @Test public void testStatsAvg() throws IOException { - String result = - executeQueryToString(String.format("source=%s | stats avg(age)", TEST_INDEX_ACCOUNT)); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"avg(age)\",\n" - + " \"type\": \"double\"\n" - + " }],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[30.171]],\n" - + " \"size\": 1\n" - + "}\n", - result); + JSONObject response = + executeQuery(String.format("source=%s | stats avg(age)", TEST_INDEX_ACCOUNT)); + verifySchema(response, schema("avg(age)", null, "double")); + verifyDataRows(response, rows(30.171D)); } @Test public void testStatsSum() throws IOException { - String result = - executeQueryToString(String.format("source=%s | stats sum(balance)", TEST_INDEX_ACCOUNT)); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"sum(balance)\",\n" - + " \"type\": \"long\"\n" - + " }],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[25714837]],\n" - + " \"size\": 1\n" - + "}\n", - result); + JSONObject response = + executeQuery(String.format("source=%s | stats sum(balance)", TEST_INDEX_ACCOUNT)); + verifySchema(response, schema("sum(balance)", null, "long")); + verifyDataRows(response, rows(25714837)); } @Test public void testStatsCount() throws IOException { - String result = - executeQueryToString( - String.format("source=%s | stats count(account_number)", TEST_INDEX_ACCOUNT)); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"count(account_number)\",\n" - + " \"type\": \"integer\"\n" - + " }],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[1000]],\n" - + " \"size\": 1\n" - + "}\n", - result); + JSONObject response = + executeQuery(String.format("source=%s | stats count(account_number)", TEST_INDEX_ACCOUNT)); + verifySchema(response, schema("count(account_number)", null, "integer")); + verifyDataRows(response, rows(1000)); } // TODO: each stats aggregate function should be tested here when implemented @Test public void testStatsNested() throws IOException { - String result = - executeQueryToString( - String.format("source=%s | stats avg(abs(age)*2) as AGE", TEST_INDEX_ACCOUNT)); - assertEquals( - "{\n" - + " \"schema\": [{\n" - + " \"name\": \"AGE\",\n" - + " \"type\": \"double\"\n" - + " }],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[60.342]],\n" - + " \"size\": 1\n" - + "}\n", - result); + JSONObject response = + executeQuery(String.format("source=%s | stats avg(abs(age)*2) as AGE", TEST_INDEX_ACCOUNT)); + verifySchema(response, schema("AGE", null, "double")); + verifyDataRows(response, rows(60.342)); } @Test public void testStatsWhere() throws IOException { - String result = executeQueryToString(String.format( - "source=%s | stats sum(balance) as a by gender | where a > 13000000", TEST_INDEX_ACCOUNT)); - assertEquals( - "{\n" - + " \"schema\": [\n" - + " {\n" - + " \"name\": \"a\",\n" - + " \"type\": \"long\"\n" - + " },\n" - + " {\n" - + " \"name\": \"gender\",\n" - + " \"type\": \"string\"\n" - + " }\n" - + " ],\n" - + " \"total\": 1,\n" - + " \"datarows\": [[\n" - + " 13082527,\n" - + " \"M\"\n" - + " ]],\n" - + " \"size\": 1\n" - + "}\n", - result - ); + JSONObject response = + executeQuery(String.format( + "source=%s | stats sum(balance) as a by gender | where a > 13000000", TEST_INDEX_ACCOUNT)); + verifySchema(response, schema("a", null, "long"), + schema("gender", null, "string")); + verifyDataRows(response, rows(13082527, "M")); } } diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/IdentifierIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/IdentifierIT.java index 3b5dc26bc5..8074bbda54 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/IdentifierIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/IdentifierIT.java @@ -67,11 +67,13 @@ public void testSpecialFieldName() throws IOException { + " \"type\": \"long\"\n" + " }\n" + " ],\n" + + " \"datarows\": [\n" + + " [\n" + + " 10,\n" + + " 30\n" + + " ]\n" + + " ],\n" + " \"total\": 1,\n" - + " \"datarows\": [[\n" - + " 10,\n" - + " 30\n" - + " ]],\n" + " \"size\": 1\n" + "}\n", executeQuery("SELECT @timestamp, `dimensions:major_version` FROM test", "jdbc") @@ -87,12 +89,18 @@ private void createIndexWithOneDoc(String... indexNames) throws IOException { private void queryAndAssertTheDoc(String sql) { assertEquals( "{\n" - + " \"schema\": [{\n" - + " \"name\": \"age\",\n" - + " \"type\": \"long\"\n" - + " }],\n" + + " \"schema\": [\n" + + " {\n" + + " \"name\": \"age\",\n" + + " \"type\": \"long\"\n" + + " }\n" + + " ],\n" + + " \"datarows\": [\n" + + " [\n" + + " 30\n" + + " ]\n" + + " ],\n" + " \"total\": 1,\n" - + " \"datarows\": [[30]],\n" + " \"size\": 1\n" + "}\n", executeQuery(sql.replace("\"", "\\\""), "jdbc") diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/MathematicalFunctionIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/MathematicalFunctionIT.java index ffc33c1324..f24de89146 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/MathematicalFunctionIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/MathematicalFunctionIT.java @@ -80,7 +80,7 @@ public void testMod() throws IOException { public void testRound() throws IOException { JSONObject result = executeQuery("select round(56.78)"); verifySchema(result, schema("round(56.78)", null, "double")); - verifyDataRows(result, rows(57)); + verifyDataRows(result, rows(57.0)); result = executeQuery("select round(56.78, 1)"); verifySchema(result, schema("round(56.78, 1)", null, "double")); @@ -88,7 +88,7 @@ public void testRound() throws IOException { result = executeQuery("select round(56.78, -1)"); verifySchema(result, schema("round(56.78, -1)", null, "double")); - verifyDataRows(result, rows(60)); + verifyDataRows(result, rows(60.0)); result = executeQuery("select round(-56)"); verifySchema(result, schema("round(-56)", null, "long")); @@ -125,7 +125,7 @@ public void testTruncate() throws IOException { result = executeQuery("select truncate(56.78, -1)"); verifySchema(result, schema("truncate(56.78, -1)", null, "double")); - verifyDataRows(result, rows(50)); + verifyDataRows(result, rows(50.0)); result = executeQuery("select truncate(-56, 1)"); verifySchema(result, schema("truncate(-56, 1)", null, "long")); diff --git a/protocol/build.gradle b/protocol/build.gradle index db00d7f8f0..94a973c645 100644 --- a/protocol/build.gradle +++ b/protocol/build.gradle @@ -11,7 +11,9 @@ repositories { dependencies { // https://github.com/google/guava/wiki/CVE-2018-10237 compile group: 'com.google.guava', name: 'guava', version: '29.0-jre' - compile group: 'org.json', name: 'json', version: '20180813' //TODO: change to other JSON lib? + compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.4' + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.4' + implementation 'com.google.code.gson:gson:2.8.6' compile project(':core') testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java index 7b383dd0ca..4f3706341b 100644 --- a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java @@ -18,9 +18,12 @@ import static com.amazon.opendistroforelasticsearch.sql.protocol.response.format.JsonResponseFormatter.Style.PRETTY; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.security.AccessController; +import java.security.PrivilegedAction; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.json.JSONObject; /** * Abstract class for all JSON formatter. @@ -42,6 +45,11 @@ public enum Style { */ private final Style style; + private static final Gson PRETTY_PRINT_GSON = + AccessController.doPrivileged( + (PrivilegedAction) () -> new GsonBuilder().setPrettyPrinting().create()); + private static final Gson GSON = AccessController.doPrivileged( + (PrivilegedAction) () -> new GsonBuilder().create()); @Override public String format(R response) { @@ -63,10 +71,9 @@ public String format(Throwable t) { */ protected abstract Object buildJsonObject(R response); - private String jsonify(Object jsonObject) { - JSONObject json = new JSONObject(jsonObject); - return (style == PRETTY) ? json.toString(2) : json.toString(); + return AccessController.doPrivileged((PrivilegedAction) () -> + (style == PRETTY) ? PRETTY_PRINT_GSON.toJson(jsonObject) : GSON.toJson(jsonObject)); } @RequiredArgsConstructor diff --git a/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java index 4090e0addd..6cb9d6c85f 100644 --- a/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java +++ b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java @@ -50,8 +50,8 @@ void formatResponse() { SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); assertEquals( "{\"schema\":[{\"name\":\"firstname\",\"type\":\"string\"}," - + "{\"name\":\"age\",\"type\":\"integer\"}]," - + "\"total\":2,\"datarows\":[[\"John\",20],[\"Smith\",30]],\"size\":2}", + + "{\"name\":\"age\",\"type\":\"integer\"}],\"datarows\":" + + "[[\"John\",20],[\"Smith\",30]],\"total\":2,\"size\":2}", formatter.format(response)); } @@ -76,7 +76,6 @@ void formatResponsePretty() { + " \"type\": \"integer\"\n" + " }\n" + " ],\n" - + " \"total\": 2,\n" + " \"datarows\": [\n" + " [\n" + " \"John\",\n" @@ -87,6 +86,7 @@ void formatResponsePretty() { + " 30\n" + " ]\n" + " ],\n" + + " \"total\": 2,\n" + " \"size\": 2\n" + "}", formatter.format(response)); @@ -104,8 +104,8 @@ void formatResponseWithMissingValue() { SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); assertEquals( "{\"schema\":[{\"name\":\"firstname\",\"type\":\"string\"}," - + "{\"name\":\"age\",\"type\":\"integer\"}],\"total\":2," - + "\"datarows\":[[\"John\",null],[\"Smith\",30]],\"size\":2}", + + "{\"name\":\"age\",\"type\":\"integer\"}]," + + "\"datarows\":[[\"John\",null],[\"Smith\",30]],\"total\":2,\"size\":2}", formatter.format(response)); } @@ -113,7 +113,7 @@ void formatResponseWithMissingValue() { void formatError() { SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); assertEquals( - "{\"reason\":\"This is an exception\",\"type\":\"RuntimeException\"}", + "{\"type\":\"RuntimeException\",\"reason\":\"This is an exception\"}", formatter.format(new RuntimeException("This is an exception"))); } @@ -122,8 +122,8 @@ void formatErrorPretty() { SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(PRETTY); assertEquals( "{\n" - + " \"reason\": \"This is an exception\",\n" - + " \"type\": \"RuntimeException\"\n" + + " \"type\": \"RuntimeException\",\n" + + " \"reason\": \"This is an exception\"\n" + "}", formatter.format(new RuntimeException("This is an exception"))); }