From d1b22db6b44a6fc61e81a0768efb74b8e04427eb Mon Sep 17 00:00:00 2001 From: David Kyle Date: Fri, 1 Mar 2019 09:48:12 +0000 Subject: [PATCH 1/5] some config objs --- .../transforms/DataFrameTransformConfig.java | 170 +++++++++++++++++ .../dataframe/transforms/QueryConfig.java | 77 ++++++++ .../transforms/pivot/AggregationConfig.java | 80 ++++++++ .../pivot/DateHistogramGroupSource.java | 171 ++++++++++++++++++ .../transforms/pivot/GroupConfig.java | 129 +++++++++++++ .../pivot/HistogramGroupSource.java | 100 ++++++++++ .../transforms/pivot/PivotConfig.java | 99 ++++++++++ .../transforms/pivot/SingleGroupSource.java | 96 ++++++++++ .../transforms/pivot/TermsGroupSource.java | 63 +++++++ .../DataFrameTransformConfigTests.java | 61 +++++++ .../transforms/QueryConfigTests.java | 62 +++++++ .../pivot/AggregationConfigTests.java | 89 +++++++++ .../pivot/DateHistogramGroupSourceTests.java | 62 +++++++ .../transforms/pivot/GroupConfigTests.java | 75 ++++++++ .../pivot/HistogramGroupSourceTests.java | 49 +++++ .../transforms/pivot/PivotConfigTests.java | 64 +++++++ .../pivot/TermsGroupSourceTests.java | 47 +++++ 17 files changed, 1494 insertions(+) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/QueryConfig.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfig.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSource.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSource.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfig.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/SingleGroupSource.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSource.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfigTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/QueryConfigTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSourceTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSourceTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfigTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSourceTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java new file mode 100644 index 0000000000000..a55c435bb934b --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java @@ -0,0 +1,170 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms; + +import org.elasticsearch.client.dataframe.transforms.pivot.PivotConfig; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.MatchAllQueryBuilder; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class DataFrameTransformConfig implements ToXContentObject { + + public static final ParseField ID = new ParseField("id"); + public static final ParseField SOURCE = new ParseField("source"); + public static final ParseField DEST = new ParseField("dest"); + public static final ParseField QUERY = new ParseField("query"); + // types of transforms + public static final ParseField PIVOT_TRANSFORM = new ParseField("pivot"); + + private final String id; + private final String source; + private final String dest; + private final QueryConfig queryConfig; + private final PivotConfig pivotConfig; + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("data_frame_transform", true, + (args) -> { + String id = (String) args[0]; + String source = (String) args[1]; + String dest = (String) args[2]; + // default handling: if the user does not specify a query, we default to match_all + QueryConfig queryConfig = (args[3] == null) ? new QueryConfig(new MatchAllQueryBuilder()) : (QueryConfig) args[3]; + PivotConfig pivotConfig = (PivotConfig) args[4]; + return new DataFrameTransformConfig(id, source, dest, queryConfig, pivotConfig); + }); + + static { + PARSER.declareString(optionalConstructorArg(), ID); + PARSER.declareString(constructorArg(), SOURCE); + PARSER.declareString(constructorArg(), DEST); + PARSER.declareObject(optionalConstructorArg(), (p, c) -> QueryConfig.fromXContent(p), QUERY); + PARSER.declareObject(optionalConstructorArg(), (p, c) -> PivotConfig.fromXContent(p), PIVOT_TRANSFORM); + } + + public static DataFrameTransformConfig fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } + + + public DataFrameTransformConfig(final String id, + final String source, + final String dest, + final QueryConfig queryConfig, + final PivotConfig pivotConfig) { + this.id = Objects.requireNonNull(id); + this.source = Objects.requireNonNull(source); + this.dest = Objects.requireNonNull(dest); + this.queryConfig = queryConfig; + this.pivotConfig = pivotConfig; + } + + public String getId() { + return id; + } + + // TODO should this function be removed? + public String getCron() { + return "*"; + } + + public String getSource() { + return source; + } + + public String getDestination() { + return dest; + } + + public PivotConfig getPivotConfig() { + return pivotConfig; + } + + public QueryConfig getQueryConfig() { + return queryConfig; + } + + public boolean isValid() { + if (queryConfig != null && queryConfig.isValid() == false) { + return false; + } + + if (pivotConfig == null || pivotConfig.isValid() == false) { + return false; + } + + return true; + } + + @Override + public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { + builder.startObject(); + builder.field(ID.getPreferredName(), id); + builder.field(SOURCE.getPreferredName(), source); + builder.field(DEST.getPreferredName(), dest); + if (queryConfig != null) { + builder.field(QUERY.getPreferredName(), queryConfig); + } + if (pivotConfig != null) { + builder.field(PIVOT_TRANSFORM.getPreferredName(), pivotConfig); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final DataFrameTransformConfig that = (DataFrameTransformConfig) other; + + return Objects.equals(this.id, that.id) + && Objects.equals(this.source, that.source) + && Objects.equals(this.dest, that.dest) + && Objects.equals(this.queryConfig, that.queryConfig) + && Objects.equals(this.pivotConfig, that.pivotConfig); + } + + @Override + public int hashCode() { + return Objects.hash(id, source, dest, queryConfig, pivotConfig); + } + + @Override + public String toString() { + return Strings.toString(this, true, true); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/QueryConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/QueryConfig.java new file mode 100644 index 0000000000000..9ab67ad0ae0a9 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/QueryConfig.java @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms; + +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.AbstractQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; + +import java.io.IOException; +import java.util.Objects; + +public class QueryConfig implements ToXContentObject { + + private final QueryBuilder query; + + public static QueryConfig fromXContent(XContentParser parser) throws IOException { + QueryBuilder query = AbstractQueryBuilder.parseInnerQueryBuilder(parser); + return new QueryConfig(query); + } + + public QueryConfig(QueryBuilder query) { + this.query = query; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + query.toXContent(builder, params); + return builder; + } + + public QueryBuilder getQuery() { + return query; + } + + @Override + public int hashCode() { + return Objects.hash(query); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final QueryConfig that = (QueryConfig) other; + + return Objects.equals(this.query, that.query); + } + + public boolean isValid() { + return this.query != null; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfig.java new file mode 100644 index 0000000000000..2ebcf68d5edb3 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfig.java @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.aggregations.AggregationBuilder; +import org.elasticsearch.search.aggregations.AggregatorFactories; + +import java.io.IOException; +import java.util.Collection; +import java.util.Objects; + +public class AggregationConfig implements ToXContentObject { + private final AggregatorFactories.Builder aggregations; + + public static AggregationConfig fromXContent(XContentParser parser) throws IOException { + if (parser.currentToken() == null) { + parser.nextToken(); + } + AggregatorFactories.Builder aggregations = AggregatorFactories.parseAggregators(parser); + return new AggregationConfig(aggregations); + } + + public AggregationConfig(AggregatorFactories.Builder aggregations) { + this.aggregations = aggregations; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + aggregations.toXContent(builder, params); + return builder; + } + + public Collection getAggregatorFactories() { + return aggregations.getAggregatorFactories(); + } + + @Override + public int hashCode() { + return Objects.hash(aggregations); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final AggregationConfig that = (AggregationConfig) other; + + return Objects.equals(this.aggregations, that.aggregations); + } + + public boolean isValid() { + return this.aggregations != null; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSource.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSource.java new file mode 100644 index 0000000000000..461b525934823 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSource.java @@ -0,0 +1,171 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class DateHistogramGroupSource extends SingleGroupSource implements ToXContentObject { + + private static final ParseField TIME_ZONE = new ParseField("time_zone"); + private static final ParseField FORMAT = new ParseField("format"); + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("date_histogram_group_source", true, (args) -> new DateHistogramGroupSource((String) args[0])); + + static { + PARSER.declareString(optionalConstructorArg(), FIELD); + PARSER.declareField((histogram, interval) -> { + if (interval instanceof Long) { + histogram.setInterval((long) interval); + } else { + histogram.setDateHistogramInterval((DateHistogramInterval) interval); + } + }, p -> { + if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) { + return p.longValue(); + } else { + return new DateHistogramInterval(p.text()); + } + }, HistogramGroupSource.INTERVAL, ObjectParser.ValueType.LONG); + PARSER.declareField(DateHistogramGroupSource::setTimeZone, p -> { + if (p.currentToken() == XContentParser.Token.VALUE_STRING) { + return ZoneId.of(p.text()); + } else { + return ZoneOffset.ofHours(p.intValue()); + } + }, TIME_ZONE, ObjectParser.ValueType.LONG); + + PARSER.declareString(DateHistogramGroupSource::setFormat, FORMAT); + } + + public static DateHistogramGroupSource fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } + + private long interval = 0; + private DateHistogramInterval dateHistogramInterval; + private String format; + private ZoneId timeZone; + + public DateHistogramGroupSource(String field) { + super(field); + } + + @Override + public Type getType() { + return Type.DATE_HISTOGRAM; + } + + public long getInterval() { + return interval; + } + + public void setInterval(long interval) { + if (interval < 1) { + throw new IllegalArgumentException("[interval] must be greater than or equal to 1."); + } + this.interval = interval; + } + + public DateHistogramInterval getDateHistogramInterval() { + return dateHistogramInterval; + } + + public void setDateHistogramInterval(DateHistogramInterval dateHistogramInterval) { + if (dateHistogramInterval == null) { + throw new IllegalArgumentException("[dateHistogramInterval] must not be null"); + } + this.dateHistogramInterval = dateHistogramInterval; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public ZoneId getTimeZone() { + return timeZone; + } + + public void setTimeZone(ZoneId timeZone) { + this.timeZone = timeZone; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (field != null) { + builder.field(FIELD.getPreferredName(), field); + } + if (dateHistogramInterval == null) { + builder.field(HistogramGroupSource.INTERVAL.getPreferredName(), interval); + } else { + builder.field(HistogramGroupSource.INTERVAL.getPreferredName(), dateHistogramInterval.toString()); + } + if (timeZone != null) { + builder.field(TIME_ZONE.getPreferredName(), timeZone.toString()); + } + if (format != null) { + builder.field(FORMAT.getPreferredName(), format); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final DateHistogramGroupSource that = (DateHistogramGroupSource) other; + + return Objects.equals(this.field, that.field) && + Objects.equals(interval, that.interval) && + Objects.equals(dateHistogramInterval, that.dateHistogramInterval) && + Objects.equals(timeZone, that.timeZone) && + Objects.equals(format, that.format); + } + + @Override + public int hashCode() { + return Objects.hash(field, interval, dateHistogramInterval, timeZone, format); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java new file mode 100644 index 0000000000000..631c26d6f7e01 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java @@ -0,0 +1,129 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +public class GroupConfig implements ToXContentObject { + + private final Map groups; + + public static GroupConfig fromXContent(final XContentParser parser) throws IOException { + LinkedHashMap groups = new LinkedHashMap<>(); + + // be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does) + XContentParser.Token token; + if (parser.currentToken() == XContentParser.Token.START_OBJECT) { + token = parser.currentToken(); + } else { + token = parser.nextToken(); + if (token != XContentParser.Token.START_OBJECT) { + throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: Expected START_OBJECT but was: " + token); + } + } + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + String destinationFieldName = parser.currentName(); + token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); + token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + SingleGroupSource.Type groupType = SingleGroupSource.Type.valueOf(parser.currentName().toUpperCase(Locale.ROOT)); + + token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); + SingleGroupSource groupSource; + switch (groupType) { + case TERMS: + groupSource = TermsGroupSource.fromXContent(parser); + break; + case HISTOGRAM: + groupSource = HistogramGroupSource.fromXContent(parser); + break; + case DATE_HISTOGRAM: + groupSource = DateHistogramGroupSource.fromXContent(parser); + break; + default: + throw new ParsingException(parser.getTokenLocation(), "invalid grouping type: " + groupType); + } + + parser.nextToken(); + + groups.put(destinationFieldName, groupSource); + } + return new GroupConfig(groups); + } + + public GroupConfig(Map groups) { + this.groups = groups; + } + + public Map getGroups() { + return groups; + } + + public boolean isValid() { + return this.groups != null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + for (Map.Entry entry : groups.entrySet()) { + builder.startObject(entry.getKey()); + builder.field(entry.getValue().getType().value(), entry.getValue()); + builder.endObject(); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final GroupConfig that = (GroupConfig) other; + + return Objects.equals(this.groups, that.groups); + } + + @Override + public int hashCode() { + return Objects.hash(groups); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSource.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSource.java new file mode 100644 index 0000000000000..764d9c15c6074 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSource.java @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class HistogramGroupSource extends SingleGroupSource implements ToXContentObject { + + protected static final ParseField INTERVAL = new ParseField("interval"); + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("histogram_group_source", true, + args -> new HistogramGroupSource((String) args[0], (double) args[1])); + + static { + PARSER.declareString(optionalConstructorArg(), FIELD); + PARSER.declareDouble(optionalConstructorArg(), INTERVAL); + } + + public static HistogramGroupSource fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } + + private final double interval; + + public HistogramGroupSource(String field, double interval) { + super(field); + if (interval <= 0) { + throw new IllegalArgumentException("[interval] must be greater than 0."); + } + this.interval = interval; + } + + @Override + public Type getType() { + return Type.HISTOGRAM; + } + + public double getInterval() { + return interval; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(); + if (field != null) { + builder.field(FIELD.getPreferredName(), field); + } + builder.field(INTERVAL.getPreferredName(), interval); + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final HistogramGroupSource that = (HistogramGroupSource) other; + + return Objects.equals(this.field, that.field) && + Objects.equals(this.interval, that.interval); + } + + @Override + public int hashCode() { + return Objects.hash(field, interval); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfig.java new file mode 100644 index 0000000000000..11362743bcc18 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfig.java @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class PivotConfig implements ToXContentObject { + + private static final ParseField GROUP_BY = new ParseField("group_by"); + private static final ParseField AGGREGATIONS = new ParseField("aggregations"); + + private final GroupConfig groups; + private final AggregationConfig aggregationConfig; + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("pivot_config", true, + args -> new PivotConfig((GroupConfig) args[0], (AggregationConfig) args[1])); + + static { + PARSER.declareObject(constructorArg(), (p, c) -> (GroupConfig.fromXContent(p)), GROUP_BY); + PARSER.declareObject(optionalConstructorArg(), (p, c) -> AggregationConfig.fromXContent(p), AGGREGATIONS); + } + + public static PivotConfig fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } + + public PivotConfig(GroupConfig groups, final AggregationConfig aggregationConfig) { + this.groups = groups; + this.aggregationConfig = aggregationConfig; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(GROUP_BY.getPreferredName(), groups); + builder.field(AGGREGATIONS.getPreferredName(), aggregationConfig); + builder.endObject(); + return builder; + } + + public AggregationConfig getAggregationConfig() { + return aggregationConfig; + } + + public GroupConfig getGroupConfig() { + return groups; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final PivotConfig that = (PivotConfig) other; + + return Objects.equals(this.groups, that.groups) && Objects.equals(this.aggregationConfig, that.aggregationConfig); + } + + @Override + public int hashCode() { + return Objects.hash(groups, aggregationConfig); + } + + public boolean isValid() { + return groups.isValid() && aggregationConfig.isValid(); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/SingleGroupSource.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/SingleGroupSource.java new file mode 100644 index 0000000000000..8168d8850e700 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/SingleGroupSource.java @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ToXContentObject; + +import java.util.Locale; +import java.util.Objects; + +public abstract class SingleGroupSource implements ToXContentObject { + + protected static final ParseField FIELD = new ParseField("field"); + + public enum Type { + TERMS(0), + HISTOGRAM(1), + DATE_HISTOGRAM(2); + + private final byte id; + + Type(int id) { + this.id = (byte) id; + } + + public byte getId() { + return id; + } + + public static Type fromId(byte id) { + switch (id) { + case 0: + return TERMS; + case 1: + return HISTOGRAM; + case 2: + return DATE_HISTOGRAM; + default: + throw new IllegalArgumentException("unknown type"); + } + } + + public String value() { + return name().toLowerCase(Locale.ROOT); + } + } + + protected final String field; + + public SingleGroupSource(final String field) { + this.field = field; + } + + public abstract Type getType(); + + public String getField() { + return field; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof SingleGroupSource == false) { + return false; + } + + final SingleGroupSource that = (SingleGroupSource) other; + + return Objects.equals(this.field, that.field); + } + + @Override + public int hashCode() { + return Objects.hash(field); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSource.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSource.java new file mode 100644 index 0000000000000..02a0caa59968e --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSource.java @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class TermsGroupSource extends SingleGroupSource implements ToXContentObject { + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("terms_group_source", true, args -> new TermsGroupSource((String) args[0])); + + static { + PARSER.declareString(optionalConstructorArg(), FIELD); + } + + public static TermsGroupSource fromXContent(final XContentParser parser) { + return PARSER.apply(parser, null); + } + + public TermsGroupSource(final String field) { + super(field); + } + + @Override + public Type getType() { + return Type.TERMS; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(); + if (field != null) { + builder.field(FIELD.getPreferredName(), field); + } + builder.endObject(); + return builder; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfigTests.java new file mode 100644 index 0000000000000..cb71be59e2e9a --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfigTests.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms; + +import org.elasticsearch.client.dataframe.transforms.pivot.PivotConfigTests; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.function.Predicate; + +public class DataFrameTransformConfigTests extends AbstractXContentTestCase { + @Override + protected DataFrameTransformConfig createTestInstance() { + return new DataFrameTransformConfig(randomAlphaOfLengthBetween(1, 10), randomAlphaOfLengthBetween(1, 10), + randomAlphaOfLengthBetween(1, 10), QueryConfigTests.randomQueryConfig(), PivotConfigTests.randomPivotConfig()); + } + + @Override + protected DataFrameTransformConfig doParseInstance(XContentParser parser) throws IOException { + return DataFrameTransformConfig.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + // allow unknown fields in the root of the object only + return field -> !field.isEmpty(); + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + SearchModule searchModule = new SearchModule(Settings.EMPTY, false, Collections.emptyList()); + return new NamedXContentRegistry(searchModule.getNamedXContents()); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/QueryConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/QueryConfigTests.java new file mode 100644 index 0000000000000..644858cab286f --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/QueryConfigTests.java @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.index.query.MatchAllQueryBuilder; +import org.elasticsearch.index.query.MatchNoneQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +import static java.util.Collections.emptyList; + +public class QueryConfigTests extends AbstractXContentTestCase { + + public static QueryConfig randomQueryConfig() { + QueryBuilder queryBuilder = randomBoolean() ? new MatchAllQueryBuilder() : new MatchNoneQueryBuilder(); + return new QueryConfig(queryBuilder); + } + + @Override + protected QueryConfig createTestInstance() { + return randomQueryConfig(); + } + + @Override + protected QueryConfig doParseInstance(XContentParser parser) throws IOException { + return QueryConfig.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + SearchModule searchModule = new SearchModule(Settings.EMPTY, false, emptyList()); + return new NamedXContentRegistry(searchModule.getNamedXContents()); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java new file mode 100644 index 0000000000000..07c839347590a --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.search.aggregations.AggregationBuilder; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.AggregatorFactories; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Collections.emptyList; + +public class AggregationConfigTests extends AbstractXContentTestCase { + + public static AggregationConfig randomAggregationConfig() { + AggregatorFactories.Builder builder = new AggregatorFactories.Builder(); + Set names = new HashSet<>(); + int numAggs = randomIntBetween(1, 4); + for (int i = 0; i < numAggs; ++i) { + AggregationBuilder aggBuilder = getRandomSupportedAggregation(); + if (names.add(aggBuilder.getName())) { + builder.addAggregator(aggBuilder); + } + } + builder.addAggregator(getRandomSupportedAggregation()); + return new AggregationConfig(builder); + } + + @Override + protected AggregationConfig createTestInstance() { + return randomAggregationConfig(); + } + + @Override + protected AggregationConfig doParseInstance(XContentParser parser) throws IOException { + return AggregationConfig.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + SearchModule searchModule = new SearchModule(Settings.EMPTY, false, emptyList()); + return new NamedXContentRegistry(searchModule.getNamedXContents()); + } + + private static AggregationBuilder getRandomSupportedAggregation() { + final int numberOfSupportedAggs = 4; + switch (randomIntBetween(1, numberOfSupportedAggs)) { + case 1: + return AggregationBuilders.avg(randomAlphaOfLengthBetween(1, 10)).field(randomAlphaOfLengthBetween(1, 10)); + case 2: + return AggregationBuilders.min(randomAlphaOfLengthBetween(1, 10)).field(randomAlphaOfLengthBetween(1, 10)); + case 3: + return AggregationBuilders.max(randomAlphaOfLengthBetween(1, 10)).field(randomAlphaOfLengthBetween(1, 10)); + case 4: + return AggregationBuilders.sum(randomAlphaOfLengthBetween(1, 10)).field(randomAlphaOfLengthBetween(1, 10)); + } + + return null; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSourceTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSourceTests.java new file mode 100644 index 0000000000000..91cf13e16e2e6 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/DateHistogramGroupSourceTests.java @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class DateHistogramGroupSourceTests extends AbstractXContentTestCase { + + public static DateHistogramGroupSource randomDateHistogramGroupSource() { + String field = randomAlphaOfLengthBetween(1, 20); + DateHistogramGroupSource dateHistogramGroupSource = new DateHistogramGroupSource(field); + if (randomBoolean()) { + dateHistogramGroupSource.setInterval(randomLongBetween(1, 10_000)); + } else { + dateHistogramGroupSource.setDateHistogramInterval(randomFrom(DateHistogramInterval.days(10), + DateHistogramInterval.minutes(1), DateHistogramInterval.weeks(1))); + } + if (randomBoolean()) { + dateHistogramGroupSource.setTimeZone(randomZone()); + } + if (randomBoolean()) { + dateHistogramGroupSource.setFormat(randomAlphaOfLength(10)); + } + return dateHistogramGroupSource; + } + + @Override + protected DateHistogramGroupSource createTestInstance() { + return randomDateHistogramGroupSource(); + } + + @Override + protected DateHistogramGroupSource doParseInstance(XContentParser parser) throws IOException { + return DateHistogramGroupSource.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java new file mode 100644 index 0000000000000..6ecc26a26ef02 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class GroupConfigTests extends AbstractXContentTestCase { + + public static GroupConfig randomGroupConfig() { + Map groups = new LinkedHashMap<>(); + + // ensure that the unlikely does not happen: 2 group_by's share the same name + Set names = new HashSet<>(); + for (int i = 0; i < randomIntBetween(1, 1); ++i) { + String targetFieldName = randomAlphaOfLengthBetween(1, 20); + if (names.add(targetFieldName)) { + SingleGroupSource groupBy; + SingleGroupSource.Type type = randomFrom(SingleGroupSource.Type.values()); + switch (type) { + case TERMS: + groupBy = TermsGroupSourceTests.randomTermsGroupSource(); + break; + case HISTOGRAM: + groupBy = HistogramGroupSourceTests.randomHistogramGroupSource(); + break; + case DATE_HISTOGRAM: + default: + groupBy = DateHistogramGroupSourceTests.randomDateHistogramGroupSource(); + } + groups.put(targetFieldName, groupBy); + } + } + + return new GroupConfig(groups); + } + + @Override + protected GroupConfig createTestInstance() { + return randomGroupConfig(); + } + + @Override + protected GroupConfig doParseInstance(XContentParser parser) throws IOException { + return GroupConfig.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSourceTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSourceTests.java new file mode 100644 index 0000000000000..18512c047a5eb --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/HistogramGroupSourceTests.java @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class HistogramGroupSourceTests extends AbstractXContentTestCase { + + public static HistogramGroupSource randomHistogramGroupSource() { + String field = randomAlphaOfLengthBetween(1, 20); + double interval = randomDoubleBetween(Math.nextUp(0), Double.MAX_VALUE, false); + return new HistogramGroupSource(field, interval); + } + + @Override + protected HistogramGroupSource doParseInstance(XContentParser parser) throws IOException { + return HistogramGroupSource.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected HistogramGroupSource createTestInstance() { + return randomHistogramGroupSource(); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfigTests.java new file mode 100644 index 0000000000000..d2e036d9f1ad2 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/PivotConfigTests.java @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.function.Predicate; + +public class PivotConfigTests extends AbstractXContentTestCase { + + public static PivotConfig randomPivotConfig() { + return new PivotConfig(GroupConfigTests.randomGroupConfig(), AggregationConfigTests.randomAggregationConfig()); + } + + @Override + protected PivotConfig createTestInstance() { + return randomPivotConfig(); + } + + @Override + protected PivotConfig doParseInstance(XContentParser parser) throws IOException { + return PivotConfig.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + // allow unknown fields in the root of the object only + return field -> !field.isEmpty(); + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + SearchModule searchModule = new SearchModule(Settings.EMPTY, false, Collections.emptyList()); + return new NamedXContentRegistry(searchModule.getNamedXContents()); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSourceTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSourceTests.java new file mode 100644 index 0000000000000..0a71566c22d30 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/TermsGroupSourceTests.java @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.elasticsearch.client.dataframe.transforms.pivot; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class TermsGroupSourceTests extends AbstractXContentTestCase { + + public static TermsGroupSource randomTermsGroupSource() { + return new TermsGroupSource(randomAlphaOfLengthBetween(1, 20)); + } + + @Override + protected TermsGroupSource createTestInstance() { + return randomTermsGroupSource(); + } + + @Override + protected TermsGroupSource doParseInstance(XContentParser parser) throws IOException { + return TermsGroupSource.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } +} From 4466a1bf452e0419f24e15f3db414c5967a169a0 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Tue, 5 Mar 2019 12:46:20 +0000 Subject: [PATCH 2/5] Remove unused getter + other feedback --- .../transforms/DataFrameTransformConfig.java | 11 ++--------- .../transforms/DataFrameTransformConfig.java | 4 ---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java index a55c435bb934b..b003174dfdc67 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/DataFrameTransformConfig.java @@ -26,7 +26,6 @@ import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.query.MatchAllQueryBuilder; import java.io.IOException; import java.util.Objects; @@ -55,14 +54,13 @@ public class DataFrameTransformConfig implements ToXContentObject { String id = (String) args[0]; String source = (String) args[1]; String dest = (String) args[2]; - // default handling: if the user does not specify a query, we default to match_all - QueryConfig queryConfig = (args[3] == null) ? new QueryConfig(new MatchAllQueryBuilder()) : (QueryConfig) args[3]; + QueryConfig queryConfig = (QueryConfig) args[3]; PivotConfig pivotConfig = (PivotConfig) args[4]; return new DataFrameTransformConfig(id, source, dest, queryConfig, pivotConfig); }); static { - PARSER.declareString(optionalConstructorArg(), ID); + PARSER.declareString(constructorArg(), ID); PARSER.declareString(constructorArg(), SOURCE); PARSER.declareString(constructorArg(), DEST); PARSER.declareObject(optionalConstructorArg(), (p, c) -> QueryConfig.fromXContent(p), QUERY); @@ -90,11 +88,6 @@ public String getId() { return id; } - // TODO should this function be removed? - public String getCron() { - return "*"; - } - public String getSource() { return source; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/DataFrameTransformConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/DataFrameTransformConfig.java index 894961c869cf4..cb659ff171c4b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/DataFrameTransformConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/DataFrameTransformConfig.java @@ -143,10 +143,6 @@ public String getId() { return id; } - public String getCron() { - return "*"; - } - public String getSource() { return source; } From ce08a38287e9608623bfabb4a0aee971311dd26d Mon Sep 17 00:00:00 2001 From: David Kyle Date: Tue, 5 Mar 2019 18:35:55 +0000 Subject: [PATCH 3/5] Lenient parsing for GroupConfig --- .../transforms/pivot/GroupConfig.java | 24 ++++++++++-- .../transforms/pivot/GroupConfigTests.java | 37 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java index 631c26d6f7e01..e8ea186746c53 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java @@ -36,13 +36,23 @@ public class GroupConfig implements ToXContentObject { private final Map groups; + /** + * Leniently parse a {@code GroupConfig}. + * Parsing is lenient in that unknown fields in the root of the + * object are ignored. Unknown group types {@link SingleGroupSource.Type} + * will cause a parsing error. + * + * @param parser The XContent parser + * @return The parsed object + * @throws IOException On parsing error + */ public static GroupConfig fromXContent(final XContentParser parser) throws IOException { LinkedHashMap groups = new LinkedHashMap<>(); // be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does) XContentParser.Token token; if (parser.currentToken() == XContentParser.Token.START_OBJECT) { - token = parser.currentToken(); + parser.currentToken(); } else { token = parser.nextToken(); if (token != XContentParser.Token.START_OBJECT) { @@ -52,8 +62,16 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - String destinationFieldName = parser.currentName(); token = parser.nextToken(); + if (token != XContentParser.Token.START_OBJECT) { + // leniently skip over key-value and array fields in the root of the object + if (token == XContentParser.Token.START_ARRAY) { + parser.skipChildren(); + } + continue; + } + String destinationFieldName = parser.currentName(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); token = parser.nextToken(); ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); @@ -75,7 +93,7 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc default: throw new ParsingException(parser.getTokenLocation(), "invalid grouping type: " + groupType); } - + // destination field end_object parser.nextToken(); groups.put(destinationFieldName, groupSource); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java index 6ecc26a26ef02..701c41b9e3c46 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java @@ -19,7 +19,11 @@ package org.elasticsearch.client.dataframe.transforms.pivot; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.test.AbstractXContentTestCase; import java.io.IOException; @@ -28,6 +32,8 @@ import java.util.Map; import java.util.Set; +import static org.hamcrest.Matchers.instanceOf; + public class GroupConfigTests extends AbstractXContentTestCase { public static GroupConfig randomGroupConfig() { @@ -35,7 +41,7 @@ public static GroupConfig randomGroupConfig() { // ensure that the unlikely does not happen: 2 group_by's share the same name Set names = new HashSet<>(); - for (int i = 0; i < randomIntBetween(1, 1); ++i) { + for (int i = 0; i < randomIntBetween(1, 4); ++i) { String targetFieldName = randomAlphaOfLengthBetween(1, 20); if (names.add(targetFieldName)) { SingleGroupSource groupBy; @@ -72,4 +78,33 @@ protected GroupConfig doParseInstance(XContentParser parser) throws IOException protected boolean supportsUnknownFields() { return false; } + + public void testLenientParsing() throws IOException { + BytesArray json = new BytesArray( + "{ " + + "\"unknown-field\":\"foo\", " + + "\"destination-field\": {" + + "\"terms\": {" + + "\"field\": \"term-field\"" + + "}" + + "}," + + "\"unknown-field-2\":\"bar\"," + + "\"destination-field2\": {" + + "\"terms\": {" + + "\"field\": \"term-field2\"" + + "}" + + "}," + + "\"array-field\" : [1.0, 2.0]" + + "}"); + XContentParser parser = JsonXContent.jsonXContent + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput()); + + GroupConfig gc = GroupConfig.fromXContent(parser); + + assertEquals(gc.getGroups().size(), 2); + assertTrue(gc.getGroups().containsKey("destination-field")); + SingleGroupSource groupSource = gc.getGroups().get("destination-field"); + assertThat(groupSource, instanceOf(TermsGroupSource.class)); + assertEquals(groupSource.getField(), "term-field"); + } } From 31b3b9f8501ea3f79056c06ddc4115f3af4d1196 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Wed, 6 Mar 2019 14:41:24 +0000 Subject: [PATCH 4/5] Allow unknown group types in lenient parser --- .../transforms/pivot/GroupConfig.java | 60 ++++++++++++++----- .../transforms/pivot/GroupConfigTests.java | 36 ++++++++++- .../transforms/pivot/GroupConfig.java | 4 +- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java index e8ea186746c53..e9c2a578a3c3f 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfig.java @@ -20,13 +20,13 @@ package org.elasticsearch.client.dataframe.transforms.pivot; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; import java.util.LinkedHashMap; -import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -51,9 +51,7 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc // be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does) XContentParser.Token token; - if (parser.currentToken() == XContentParser.Token.START_OBJECT) { - parser.currentToken(); - } else { + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { token = parser.nextToken(); if (token != XContentParser.Token.START_OBJECT) { throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: Expected START_OBJECT but was: " + token); @@ -70,37 +68,64 @@ public static GroupConfig fromXContent(final XContentParser parser) throws IOExc } continue; } - String destinationFieldName = parser.currentName(); + String destinationFieldName = parser.currentName(); ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); token = parser.nextToken(); ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - SingleGroupSource.Type groupType = SingleGroupSource.Type.valueOf(parser.currentName().toUpperCase(Locale.ROOT)); + String groupType = parser.currentName(); token = parser.nextToken(); - ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); - SingleGroupSource groupSource; + if (token != XContentParser.Token.START_OBJECT) { + // need to consume up to dest field end obj + consumeUntilEndObject(parser, 1); + continue; + } + + SingleGroupSource groupSource = null; switch (groupType) { - case TERMS: + case "terms": groupSource = TermsGroupSource.fromXContent(parser); break; - case HISTOGRAM: + case "histogram": groupSource = HistogramGroupSource.fromXContent(parser); break; - case DATE_HISTOGRAM: + case "date_histogram": groupSource = DateHistogramGroupSource.fromXContent(parser); break; default: - throw new ParsingException(parser.getTokenLocation(), "invalid grouping type: " + groupType); + // not a valid group source. Consume up to the dest field end object + consumeUntilEndObject(parser, 2); } - // destination field end_object - parser.nextToken(); - groups.put(destinationFieldName, groupSource); + if (groupSource != null) { + groups.put(destinationFieldName, groupSource); + // destination field end_object + parser.nextToken(); + } } return new GroupConfig(groups); } + /** + * Consume tokens from the parser until {@code endObjectCount} of end object + * tokens have been read. Nested objects that start and end inside the current + * field are skipped and do contribute to the end object count. + * @param parser The XContent parser + * @param endObjectCount Number of end object tokens to consume + * @throws IOException On parsing error + */ + private static void consumeUntilEndObject(XContentParser parser, int endObjectCount) throws IOException { + do { + XContentParser.Token token = parser.nextToken(); + if (token == XContentParser.Token.START_OBJECT) { + endObjectCount++; + } else if (token == XContentParser.Token.END_OBJECT) { + endObjectCount--; + } + } while (endObjectCount != 0); + } + public GroupConfig(Map groups) { this.groups = groups; } @@ -144,4 +169,9 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(groups); } + + @Override + public String toString() { + return Strings.toString(this, true, true); + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java index 701c41b9e3c46..ab61f6abf489b 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/GroupConfigTests.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import static org.hamcrest.Matchers.instanceOf; @@ -76,7 +77,12 @@ protected GroupConfig doParseInstance(XContentParser parser) throws IOException @Override protected boolean supportsUnknownFields() { - return false; + return true; + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return field -> !field.isEmpty(); } public void testLenientParsing() throws IOException { @@ -107,4 +113,32 @@ public void testLenientParsing() throws IOException { assertThat(groupSource, instanceOf(TermsGroupSource.class)); assertEquals(groupSource.getField(), "term-field"); } + + public void testLenientParsingUnknowGroupType() throws IOException { + BytesArray json = new BytesArray( + "{ " + + "\"destination-field1\": {" + + "\"newgroup\": {" + + "\"field1\": \"bar\"," + + "\"field2\": \"foo\"" + + "}" + + "}," + + "\"unknown-field\":\"bar\"," + + "\"destination-field2\": {" + + "\"terms\": {" + + "\"field\": \"term-field\"" + + "}" + + "}" + + "}"); + XContentParser parser = JsonXContent.jsonXContent + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput()); + + GroupConfig gc = GroupConfig.fromXContent(parser); + + assertEquals(gc.getGroups().size(), 1); + assertTrue(gc.getGroups().containsKey("destination-field2")); + SingleGroupSource groupSource = gc.getGroups().get("destination-field2"); + assertThat(groupSource, instanceOf(TermsGroupSource.class)); + assertEquals(groupSource.getField(), "term-field"); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot/GroupConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot/GroupConfig.java index 0e8471368f4d9..de394fa3f199f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot/GroupConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/pivot/GroupConfig.java @@ -139,9 +139,7 @@ private static Map> parseGroupConfig(final XContent // be parsing friendly, whether the token needs to be advanced or not (similar to what ObjectParser does) XContentParser.Token token; - if (parser.currentToken() == XContentParser.Token.START_OBJECT) { - token = parser.currentToken(); - } else { + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { token = parser.nextToken(); if (token != XContentParser.Token.START_OBJECT) { throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: Expected START_OBJECT but was: " + token); From cfae00f9a0a6c985bbf6f97ef2aa92f5bef4d1b0 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Thu, 7 Mar 2019 13:05:09 +0000 Subject: [PATCH 5/5] Fix test error where 2 aggs can have the same name --- .../dataframe/transforms/pivot/AggregationConfigTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java index 07c839347590a..210dc59329c02 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/dataframe/transforms/pivot/AggregationConfigTests.java @@ -46,7 +46,6 @@ public static AggregationConfig randomAggregationConfig() { builder.addAggregator(aggBuilder); } } - builder.addAggregator(getRandomSupportedAggregation()); return new AggregationConfig(builder); }