From 229da0d6bb1bc5393757232c11836f68bf79c63a Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Wed, 18 Jun 2025 16:06:06 +0200 Subject: [PATCH 01/15] apply spotless formatting --- build.gradle | 26 ++++ .../plugin/PathHierarchyAggregation.java | 22 ++- .../DateHierarchyAggregationBuilder.java | 105 ++++++------- .../DateHierarchyAggregationSupplier.java | 24 +-- .../bucket/DateHierarchyAggregator.java | 70 +++++---- .../DateHierarchyAggregatorFactory.java | 99 ++++++++----- .../bucket/InternalDateHierarchy.java | 68 +++++---- .../bucket/InternalPathHierarchy.java | 107 +++++++++----- .../PathHierarchyAggregationBuilder.java | 111 +++++++------- .../PathHierarchyAggregationSupplier.java | 30 ++-- .../bucket/PathHierarchyAggregator.java | 79 ++++++---- .../PathHierarchyAggregatorFactory.java | 138 +++++++++++------- .../aggregations/bucket/PathSortedTree.java | 22 ++- .../elasticsearch/PathHierarchyTests.java | 16 +- .../elasticsearch/RestApiYamlIT.java | 3 +- 15 files changed, 535 insertions(+), 385 deletions(-) diff --git a/build.gradle b/build.gradle index 19a82d5..749017e 100644 --- a/build.gradle +++ b/build.gradle @@ -9,11 +9,26 @@ buildscript { } } +// the spotless plugin is dedicated to format the code +plugins { + id("com.diffplug.spotless") version "7.0.4" +} + repositories { mavenLocal() mavenCentral() } +// add -Xlint:deprecation to check the Elasticsearch deprecation warning at compile-time +allprojects { + gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:deprecation" + options.compilerArgs << "-Xlint:unchecked" + } + } +} + group = 'org.elasticsearch.plugin' version = "${plugin_version}" @@ -24,6 +39,17 @@ apply plugin: 'idea' apply plugin: 'elasticsearch.esplugin' apply plugin: 'elasticsearch.yaml-rest-test' +// automatic formatting configuration (the same configuration as elasticsearch) +spotless { + java { + importOrderFile('config/elastic.importorder') // import order file as exported from elastic + eclipse().configFile('config/formatterConfig.xml') + trimTrailingWhitespace() + target 'src/**/*.java' + } +} + +check.dependsOn spotlessCheck esplugin { name 'pathhierarchy-aggregation' diff --git a/src/main/java/org/opendatasoft/elasticsearch/plugin/PathHierarchyAggregation.java b/src/main/java/org/opendatasoft/elasticsearch/plugin/PathHierarchyAggregation.java index 63861bf..8e2b6f5 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/plugin/PathHierarchyAggregation.java +++ b/src/main/java/org/opendatasoft/elasticsearch/plugin/PathHierarchyAggregation.java @@ -14,20 +14,18 @@ public class PathHierarchyAggregation extends Plugin implements SearchPlugin { public ArrayList getAggregations() { ArrayList r = new ArrayList<>(); r.add( - new AggregationSpec( - PathHierarchyAggregationBuilder.NAME, - PathHierarchyAggregationBuilder::new, - PathHierarchyAggregationBuilder.PARSER) - .addResultReader(InternalPathHierarchy::new) - .setAggregatorRegistrar(PathHierarchyAggregationBuilder::registerAggregators) + new AggregationSpec( + PathHierarchyAggregationBuilder.NAME, + PathHierarchyAggregationBuilder::new, + PathHierarchyAggregationBuilder.PARSER + ).addResultReader(InternalPathHierarchy::new).setAggregatorRegistrar(PathHierarchyAggregationBuilder::registerAggregators) ); r.add( - new AggregationSpec( - DateHierarchyAggregationBuilder.NAME, - DateHierarchyAggregationBuilder::new, - DateHierarchyAggregationBuilder.PARSER) - .addResultReader(InternalDateHierarchy::new) - .setAggregatorRegistrar(DateHierarchyAggregationBuilder::registerAggregators) + new AggregationSpec( + DateHierarchyAggregationBuilder.NAME, + DateHierarchyAggregationBuilder::new, + DateHierarchyAggregationBuilder.PARSER + ).addResultReader(InternalDateHierarchy::new).setAggregatorRegistrar(DateHierarchyAggregationBuilder::registerAggregators) ); return r; } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java index 4642461..7bd454b 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java @@ -1,29 +1,29 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; import org.elasticsearch.Version; -import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.common.Rounding; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.time.DateFormatter; -import org.elasticsearch.xcontent.ObjectParser; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.index.mapper.DateFieldMapper; -import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalOrder; +import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.xcontent.ObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; import java.time.ZoneId; @@ -36,15 +36,13 @@ import static java.util.Collections.unmodifiableMap; - /** * The builder of the aggregatorFactory. Also implements the parsing of the request. */ public class DateHierarchyAggregationBuilder extends ValuesSourceAggregationBuilder { public static final String NAME = "date_hierarchy"; public static final ValuesSourceRegistry.RegistryKey REGISTRY_KEY = - new ValuesSourceRegistry.RegistryKey<>(NAME, DateHierarchyAggregationSupplier.class); - + new ValuesSourceRegistry.RegistryKey<>(NAME, DateHierarchyAggregationSupplier.class); public static final ParseField INTERVAL_FIELD = new ParseField("interval"); public static final ParseField ORDER_FIELD = new ParseField("order"); @@ -52,7 +50,6 @@ public class DateHierarchyAggregationBuilder extends ValuesSourceAggregationBuil public static final ParseField SHARD_SIZE_FIELD = new ParseField("shard_size"); public static final ParseField MIN_DOC_COUNT_FIELD = new ParseField("min_doc_count"); - public static final Map INTERVAL_CONFIG; static { Map dateFieldUnits = new LinkedHashMap<>(); @@ -88,13 +85,19 @@ public PreparedRounding(RoundingInfo roundingInfo, Rounding.Prepared prepared) { public List buildRoundings() { List roundings = new ArrayList<>(); - ZoneId timeZone = timeZone() == null ? ZoneOffset.UTC: timeZone(); + ZoneId timeZone = timeZone() == null ? ZoneOffset.UTC : timeZone(); long now = System.currentTimeMillis(); for (String interval : INTERVAL_CONFIG.keySet()) { - RoundingInfo ri = new RoundingInfo(interval, createRounding(INTERVAL_CONFIG.get(interval).dateTimeUnit), - new DocValueFormat.DateTime(DateFormatter.forPattern(INTERVAL_CONFIG.get(interval).format), timeZone, - DateFieldMapper.Resolution.MILLISECONDS)); + RoundingInfo ri = new RoundingInfo( + interval, + createRounding(INTERVAL_CONFIG.get(interval).dateTimeUnit), + new DocValueFormat.DateTime( + DateFormatter.forPattern(INTERVAL_CONFIG.get(interval).format), + timeZone, + DateFieldMapper.Resolution.MILLISECONDS + ) + ); roundings.add(new PreparedRounding(ri, ri.rounding.prepareForUnknown())); if (interval.equals(interval())) { @@ -112,7 +115,7 @@ public static class RoundingInfo implements Writeable { public RoundingInfo(String interval, Rounding rounding, DocValueFormat docValueFormat) { this.interval = interval; - this.rounding = rounding; + this.rounding = rounding; this.format = docValueFormat; } @@ -130,10 +133,12 @@ public void writeTo(StreamOutput out) throws IOException { } } - public static final DateHierarchyAggregator.BucketCountThresholds DEFAULT_BUCKET_COUNT_THRESHOLDS = new - DateHierarchyAggregator.BucketCountThresholds(10, -1); - public static final ObjectParser PARSER = - ObjectParser.fromBuilder(NAME, DateHierarchyAggregationBuilder::new); + public static final DateHierarchyAggregator.BucketCountThresholds DEFAULT_BUCKET_COUNT_THRESHOLDS = + new DateHierarchyAggregator.BucketCountThresholds(10, -1); + public static final ObjectParser PARSER = ObjectParser.fromBuilder( + NAME, + DateHierarchyAggregationBuilder::new + ); static { ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, true); @@ -151,8 +156,7 @@ public void writeTo(StreamOutput out) throws IOException { PARSER.declareInt(DateHierarchyAggregationBuilder::size, SIZE_FIELD); PARSER.declareLong(DateHierarchyAggregationBuilder::minDocCount, MIN_DOC_COUNT_FIELD); PARSER.declareInt(DateHierarchyAggregationBuilder::shardSize, SHARD_SIZE_FIELD); - PARSER.declareObjectArray(DateHierarchyAggregationBuilder::order, (p, c) -> InternalOrder.Parser.parseOrderParam(p), - ORDER_FIELD); + PARSER.declareObjectArray(DateHierarchyAggregationBuilder::order, (p, c) -> InternalOrder.Parser.parseOrderParam(p), ORDER_FIELD); } public static AggregationBuilder parse(String aggregationName, XContentParser parser) throws IOException { @@ -168,8 +172,8 @@ public static void registerAggregators(ValuesSourceRegistry.Builder builder) { private String interval = "years"; private BucketOrder order = BucketOrder.compound(BucketOrder.count(false)); // automatically adds tie-breaker key asc order private DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new DateHierarchyAggregator.BucketCountThresholds( - DEFAULT_BUCKET_COUNT_THRESHOLDS); - + DEFAULT_BUCKET_COUNT_THRESHOLDS + ); private DateHierarchyAggregationBuilder(String name) { super(name); @@ -193,8 +197,7 @@ public DateHierarchyAggregationBuilder(StreamInput in) throws IOException { timeZone = in.readOptionalZoneId(); } - private DateHierarchyAggregationBuilder(DateHierarchyAggregationBuilder clone, Builder factoriesBuilder, - Map metaData) { + private DateHierarchyAggregationBuilder(DateHierarchyAggregationBuilder clone, Builder factoriesBuilder, Map metaData) { super(clone, factoriesBuilder, metaData); order = clone.order; minDocCount = clone.minDocCount; @@ -275,7 +278,7 @@ private DateHierarchyAggregationBuilder order(BucketOrder order) { if (order == null) { throw new IllegalArgumentException("[order] must not be null: [" + name + "]"); } - if(order instanceof InternalOrder.CompoundOrder || InternalOrder.isKeyOrder(order)) { + if (order instanceof InternalOrder.CompoundOrder || InternalOrder.isKeyOrder(order)) { this.order = order; // if order already contains a tie-breaker we are good to go } else { // otherwise add a tie-breaker by using a compound order this.order = BucketOrder.compound(order); @@ -292,7 +295,6 @@ private DateHierarchyAggregationBuilder order(List orders) { return this; } - /** * Sets the size - indicating how many term buckets should be returned * (defaults to 10) @@ -310,7 +312,8 @@ public DateHierarchyAggregationBuilder size(int size) { public DateHierarchyAggregationBuilder minDocCount(long minDocCount) { if (minDocCount < 0) { throw new IllegalArgumentException( - "[minDocCount] must be greater than or equal to 0. Found [" + minDocCount + "] in [" + name + "]"); + "[minDocCount] must be greater than or equal to 0. Found [" + minDocCount + "] in [" + name + "]" + ); } this.minDocCount = minDocCount; return this; @@ -336,8 +339,7 @@ public BucketCardinality bucketCardinality() { */ public DateHierarchyAggregationBuilder shardSize(int shardSize) { if (shardSize <= 0) { - throw new IllegalArgumentException( - "[shardSize] must be greater than 0. Found [" + shardSize + "] in [" + name + "]"); + throw new IllegalArgumentException("[shardSize] must be greater than 0. Found [" + shardSize + "] in [" + name + "]"); } bucketCountThresholds.setShardSize(shardSize); return this; @@ -351,25 +353,27 @@ public int shardSize() { } @Override - protected ValuesSourceAggregatorFactory innerBuild(AggregationContext context, - ValuesSourceConfig config, - AggregatorFactory parent, - Builder subFactoriesBuilder) throws IOException { - + protected ValuesSourceAggregatorFactory innerBuild( + AggregationContext context, + ValuesSourceConfig config, + AggregatorFactory parent, + Builder subFactoriesBuilder + ) throws IOException { final List preparedRoundings = buildRoundings(); return new DateHierarchyAggregatorFactory( - name, - config, - order, - preparedRoundings, - minDocCount, - bucketCountThresholds, - context, - parent, - subFactoriesBuilder, - metadata); + name, + config, + order, + preparedRoundings, + minDocCount, + bucketCountThresholds, + context, + parent, + subFactoriesBuilder, + metadata + ); } @Override @@ -398,10 +402,10 @@ public boolean equals(Object obj) { if (!super.equals(obj)) return false; DateHierarchyAggregationBuilder other = (DateHierarchyAggregationBuilder) obj; return Objects.equals(interval, other.interval) - && Objects.equals(order, other.order) - && Objects.equals(minDocCount, other.minDocCount) - && Objects.equals(bucketCountThresholds, other.bucketCountThresholds) - && Objects.equals(timeZone, other.timeZone); + && Objects.equals(order, other.order) + && Objects.equals(minDocCount, other.minDocCount) + && Objects.equals(bucketCountThresholds, other.bucketCountThresholds) + && Objects.equals(timeZone, other.timeZone); } @Override @@ -410,6 +414,7 @@ public String getType() { } @Override - protected ValuesSourceRegistry.RegistryKey getRegistryKey() { return REGISTRY_KEY; } + protected ValuesSourceRegistry.RegistryKey getRegistryKey() { + return REGISTRY_KEY; + } } - diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationSupplier.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationSupplier.java index fba09df..4bc4a2a 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationSupplier.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationSupplier.java @@ -13,15 +13,17 @@ @FunctionalInterface public interface DateHierarchyAggregationSupplier { - Aggregator build(String name, - AggregatorFactories factories, - BucketOrder order, - List roundingsInfo, - long minDocCount, - DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds, - ValuesSourceConfig valuesSourceConfig, - SearchContext aggregationContext, - Aggregator parent, - CardinalityUpperBound cardinality, - Map metadata) throws IOException; + Aggregator build( + String name, + AggregatorFactories factories, + BucketOrder order, + List roundingsInfo, + long minDocCount, + DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds, + ValuesSourceConfig valuesSourceConfig, + SearchContext aggregationContext, + Aggregator parent, + CardinalityUpperBound cardinality, + Map metadata + ) throws IOException; } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java index 0dc4db1..e993d5a 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java @@ -8,10 +8,8 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.core.Releasables; import org.elasticsearch.common.util.BytesRefHash; -import org.elasticsearch.xcontent.ToXContentFragment; -import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.core.Releasables; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.BucketOrder; @@ -20,8 +18,10 @@ import org.elasticsearch.search.aggregations.LeafBucketCollector; import org.elasticsearch.search.aggregations.LeafBucketCollectorBase; import org.elasticsearch.search.aggregations.bucket.BucketsAggregator; -import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; import java.util.Arrays; @@ -31,26 +31,26 @@ import java.util.Map; import java.util.Objects; - public class DateHierarchyAggregator extends BucketsAggregator { - public DateHierarchyAggregator(String name, - AggregatorFactories factories, - AggregationContext context, - ValuesSource.Numeric valuesSource, - BucketOrder order, - long minDocCount, - BucketCountThresholds bucketCountThresholds, - List preparedRoundings, - Aggregator parent, - CardinalityUpperBound cardinalityUpperBound, - Map metadata + public DateHierarchyAggregator( + String name, + AggregatorFactories factories, + AggregationContext context, + ValuesSource.Numeric valuesSource, + BucketOrder order, + long minDocCount, + BucketCountThresholds bucketCountThresholds, + List preparedRoundings, + Aggregator parent, + CardinalityUpperBound cardinalityUpperBound, + Map metadata ) throws IOException { super(name, factories, context, parent, cardinalityUpperBound, metadata); this.valuesSource = valuesSource; this.preparedRoundings = preparedRoundings; this.minDocCount = minDocCount; - bucketOrds = new BytesRefHash(1, context.bigArrays()); + bucketOrds = new BytesRefHash(1, context.bigArrays()); this.bucketCountThresholds = bucketCountThresholds; order.validate(this); this.order = order; @@ -134,8 +134,7 @@ public boolean equals(Object obj) { return false; } DateHierarchyAggregator.BucketCountThresholds other = (DateHierarchyAggregator.BucketCountThresholds) obj; - return Objects.equals(requiredSize, other.requiredSize) - && Objects.equals(shardSize, other.shardSize); + return Objects.equals(requiredSize, other.requiredSize) && Objects.equals(shardSize, other.shardSize); } } @@ -171,7 +170,7 @@ public void collect(int doc, long bucket) throws IOException { for (int i = 0; i < valuesCount; ++i) { long value = values.nextValue(); String path = ""; - for (DateHierarchyAggregationBuilder.PreparedRounding preparedRounding: preparedRoundings) { + for (DateHierarchyAggregationBuilder.PreparedRounding preparedRounding : preparedRoundings) { long roundedValue = preparedRounding.prepared.round(value); path += preparedRounding.roundingInfo.format.format(roundedValue).toString(); long bucketOrd = bucketOrds.add(new BytesRef(path)); @@ -231,25 +230,36 @@ public InternalAggregation[] buildAggregations(long[] owningBucketOrdinals) thro otherHierarchyNodes -= 1; } - results[ordIdx] = new InternalDateHierarchy(name, Arrays.asList(topBucketsPerOrd[ordIdx]), order, - minDocCount, bucketCountThresholds.getRequiredSize(), bucketCountThresholds.getShardSize(), - otherHierarchyNodes, metadata()); + results[ordIdx] = new InternalDateHierarchy( + name, + Arrays.asList(topBucketsPerOrd[ordIdx]), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + otherHierarchyNodes, + metadata() + ); } // Build sub-aggregations for pruned buckets - buildSubAggsForAllBuckets( - topBucketsPerOrd, - b -> b.bucketOrd, - (b, aggregations) -> b.aggregations = aggregations - ); + buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); return results; } @Override public InternalAggregation buildEmptyAggregation() { - return new InternalDateHierarchy(name, null, order, minDocCount, bucketCountThresholds.getRequiredSize(), - bucketCountThresholds.getShardSize(), 0, metadata()); + return new InternalDateHierarchy( + name, + null, + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + 0, + metadata() + ); } @Override diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregatorFactory.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregatorFactory.java index 2a96c92..eb9b885 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregatorFactory.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregatorFactory.java @@ -10,11 +10,11 @@ import org.elasticsearch.search.aggregations.NonCollectingAggregator; import org.elasticsearch.search.aggregations.bucket.BucketUtils; import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; +import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; -import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; -import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; @@ -33,16 +33,17 @@ class DateHierarchyAggregatorFactory extends ValuesSourceAggregatorFactory { private List preparedRoundings; private final DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds; - DateHierarchyAggregatorFactory(String name, - ValuesSourceConfig config, - BucketOrder order, - List preparedRoundings, - long minDocCount, - DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds, - AggregationContext context, - AggregatorFactory parent, - AggregatorFactories.Builder subFactoriesBuilder, - Map metadata + DateHierarchyAggregatorFactory( + String name, + ValuesSourceConfig config, + BucketOrder order, + List preparedRoundings, + long minDocCount, + DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds, + AggregationContext context, + AggregatorFactory parent, + AggregatorFactories.Builder subFactoriesBuilder, + Map metadata ) throws IOException { super(name, config, context, parent, subFactoriesBuilder, metadata); this.order = order; @@ -52,25 +53,37 @@ class DateHierarchyAggregatorFactory extends ValuesSourceAggregatorFactory { } public static void registerAggregators(ValuesSourceRegistry.Builder builder) { - builder.register(DateHierarchyAggregationBuilder.REGISTRY_KEY, CoreValuesSourceType.DATE, (name, - factories, - order, - roundingsInfo, - minDocCount, - bucketCountThresholds, - valuesSourceConfig, - aggregationContext, - parent, - cardinality, - metadata) -> null, - true); + builder.register( + DateHierarchyAggregationBuilder.REGISTRY_KEY, + CoreValuesSourceType.DATE, + ( + name, + factories, + order, + roundingsInfo, + minDocCount, + bucketCountThresholds, + valuesSourceConfig, + aggregationContext, + parent, + cardinality, + metadata) -> null, + true + ); } @Override - protected Aggregator createUnmapped(Aggregator parent, - Map metadata) throws IOException { - final InternalAggregation aggregation = new InternalDateHierarchy(name, new ArrayList<>(), order, minDocCount, - bucketCountThresholds.getRequiredSize(), bucketCountThresholds.getShardSize(), 0, metadata); + protected Aggregator createUnmapped(Aggregator parent, Map metadata) throws IOException { + final InternalAggregation aggregation = new InternalDateHierarchy( + name, + new ArrayList<>(), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + 0, + metadata + ); return new NonCollectingAggregator(name, context, parent, factories, metadata) { { // even in the case of an unmapped aggregator, validate the @@ -79,18 +92,21 @@ protected Aggregator createUnmapped(Aggregator parent, } @Override - public InternalAggregation buildEmptyAggregation() { return aggregation; } + public InternalAggregation buildEmptyAggregation() { + return aggregation; + } }; } @Override - protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound cardinality, Map metadata - ) throws IOException { + protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound cardinality, Map metadata) + throws IOException { - DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new - DateHierarchyAggregator.BucketCountThresholds(this.bucketCountThresholds); + DateHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new DateHierarchyAggregator.BucketCountThresholds( + this.bucketCountThresholds + ); if (!InternalOrder.isKeyOrder(order) - && bucketCountThresholds.getShardSize() == DateHierarchyAggregationBuilder.DEFAULT_BUCKET_COUNT_THRESHOLDS.getShardSize()) { + && bucketCountThresholds.getShardSize() == DateHierarchyAggregationBuilder.DEFAULT_BUCKET_COUNT_THRESHOLDS.getShardSize()) { // The user has not made a shardSize selection. Use default // heuristic to avoid any wrong-ranking caused by distributed // counting @@ -98,8 +114,17 @@ protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound c } bucketCountThresholds.ensureValidity(); return new DateHierarchyAggregator( - name, factories, context, (ValuesSource.Numeric) config.getValuesSource(), - order, minDocCount, bucketCountThresholds, preparedRoundings, parent, cardinality, metadata); + name, + factories, + context, + (ValuesSource.Numeric) config.getValuesSource(), + order, + minDocCount, + bucketCountThresholds, + preparedRoundings, + parent, + cardinality, + metadata + ); } } - diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java index c46e978..835797b 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java @@ -3,7 +3,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalAggregation; @@ -12,6 +11,7 @@ import org.elasticsearch.search.aggregations.InternalOrder; import org.elasticsearch.search.aggregations.KeyComparable; import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; +import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; import java.util.ArrayList; @@ -26,15 +26,13 @@ * which extends {@link org.elasticsearch.search.aggregations.Aggregation}. * Mainly, returns the builder and makes the reduce of buckets. */ -public class InternalDateHierarchy extends InternalMultiBucketAggregation { +public class InternalDateHierarchy extends InternalMultiBucketAggregation { /** * The bucket class of InternalDateHierarchy. * @see MultiBucketsAggregation.Bucket */ - public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucket implements - KeyComparable { + public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucket implements KeyComparable { BytesRef key; String name; @@ -64,7 +62,7 @@ public InternalBucket(StreamInput in) throws IOException { level = in.readInt(); int pathsSize = in.readInt(); paths = new String[pathsSize]; - for (int i=0; i < pathsSize; i++) { + for (int i = 0; i < pathsSize; i++) { paths[i] = in.readString(); } } @@ -80,7 +78,7 @@ public void writeTo(StreamOutput out) throws IOException { aggregations.writeTo(out); out.writeInt(level); out.writeInt(paths.length); - for (String path: paths) { + for (String path : paths) { out.writeString(path); } } @@ -120,7 +118,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } } - private List buckets; private BucketOrder order; private final int requiredSize; @@ -129,14 +126,14 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws private final long minDocCount; public InternalDateHierarchy( - String name, - List buckets, - BucketOrder order, - long minDocCount, - int requiredSize, - int shardSize, - long otherHierarchyNodes, - Map metadata + String name, + List buckets, + BucketOrder order, + long minDocCount, + int requiredSize, + int shardSize, + long otherHierarchyNodes, + Map metadata ) { super(name, metadata); this.buckets = buckets; @@ -159,7 +156,7 @@ public InternalDateHierarchy(StreamInput in) throws IOException { otherHierarchyNodes = in.readVLong(); int bucketsSize = in.readInt(); this.buckets = new ArrayList<>(bucketsSize); - for (int i=0; i buckets) { return new InternalDateHierarchy( - this.name, buckets, order, minDocCount, requiredSize, shardSize, otherHierarchyNodes, - this.metadata); + this.name, + buckets, + order, + minDocCount, + requiredSize, + shardSize, + otherHierarchyNodes, + this.metadata + ); } @Override @@ -252,8 +256,16 @@ public InternalDateHierarchy reduce(List aggregations, Redu } long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; - return new InternalDateHierarchy(getName(), ordered.getAsList(), order, minDocCount, requiredSize, shardSize, - sum_other_hierarchy_nodes, getMetadata()); + return new InternalDateHierarchy( + getName(), + ordered.getAsList(), + order, + minDocCount, + requiredSize, + shardSize, + sum_other_hierarchy_nodes, + getMetadata() + ); } @Override @@ -306,7 +318,7 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th } if (currentBucket != null) { - for (int i=0; i < currentBucket.level; i++) { + for (int i = 0; i < currentBucket.level; i++) { builder.endObject(); builder.endArray(); builder.endObject(); @@ -327,10 +339,10 @@ public int hashCode() { public boolean equals(Object obj) { InternalDateHierarchy that = (InternalDateHierarchy) obj; return Objects.equals(buckets, that.buckets) - && Objects.equals(order, that.order) - && Objects.equals(minDocCount, that.minDocCount) - && Objects.equals(requiredSize, that.requiredSize) - && Objects.equals(shardSize, that.shardSize) - && Objects.equals(otherHierarchyNodes, that.otherHierarchyNodes); + && Objects.equals(order, that.order) + && Objects.equals(minDocCount, that.minDocCount) + && Objects.equals(requiredSize, that.requiredSize) + && Objects.equals(shardSize, that.shardSize) + && Objects.equals(otherHierarchyNodes, that.otherHierarchyNodes); } } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java index 3c3651b..e35e98e 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java @@ -1,10 +1,8 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.BucketOrder; @@ -14,10 +12,12 @@ import org.elasticsearch.search.aggregations.InternalOrder; import org.elasticsearch.search.aggregations.KeyComparable; import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -28,8 +28,7 @@ * An internal implementation of {@link InternalMultiBucketAggregation} which extends {@link Aggregation}. * Mainly, returns the builder and makes the reduce of buckets. */ -public class InternalPathHierarchy extends InternalMultiBucketAggregation { +public class InternalPathHierarchy extends InternalMultiBucketAggregation { protected static final ParseField SUM_OF_OTHER_HIERARCHY_NODES = new ParseField("sum_other_hierarchy_nodes"); protected static final ParseField PATHS = new ParseField("path"); @@ -37,8 +36,7 @@ public class InternalPathHierarchy extends InternalMultiBucketAggregation { + public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucket implements KeyComparable { BytesRef termBytes; long bucketOrd; @@ -49,8 +47,15 @@ public static class InternalBucket extends InternalMultiBucketAggregation.Intern protected int minDepth; protected String basename; - public InternalBucket(long docCount, InternalAggregations aggregations, String basename, - BytesRef term, int level, int minDepth, String[] paths) { + public InternalBucket( + long docCount, + InternalAggregations aggregations, + String basename, + BytesRef term, + int level, + int minDepth, + String[] paths + ) { termBytes = term; this.docCount = docCount; this.aggregations = aggregations; @@ -72,7 +77,7 @@ public InternalBucket(StreamInput in) throws IOException { basename = in.readString(); int pathsSize = in.readInt(); paths = new String[pathsSize]; - for (int i=0; i < pathsSize; i++) { + for (int i = 0; i < pathsSize; i++) { paths[i] = in.readString(); } } @@ -89,7 +94,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeInt(minDepth); out.writeString(basename); out.writeInt(paths.length); - for (String path: paths) { + for (String path : paths) { out.writeString(path); } } @@ -129,7 +134,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } } - private List buckets; private BytesRef separator; private BucketOrder order; @@ -139,15 +143,15 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws private final long minDocCount; public InternalPathHierarchy( - String name, - List buckets, - BucketOrder order, - long minDocCount, - int requiredSize, - int shardSize, - long otherHierarchyNodes, - BytesRef separator, - Map metadata + String name, + List buckets, + BucketOrder order, + long minDocCount, + int requiredSize, + int shardSize, + long otherHierarchyNodes, + BytesRef separator, + Map metadata ) { super(name, metadata); this.buckets = buckets; @@ -172,7 +176,7 @@ public InternalPathHierarchy(StreamInput in) throws IOException { separator = in.readBytesRef(); int bucketsSize = in.readInt(); this.buckets = new ArrayList<>(bucketsSize); - for (int i=0; i buckets) { - return new InternalPathHierarchy(this.name, buckets, order, minDocCount, requiredSize, shardSize, otherHierarchyNodes, - this.separator, this.metadata); + return new InternalPathHierarchy( + this.name, + buckets, + order, + minDocCount, + requiredSize, + shardSize, + otherHierarchyNodes, + this.separator, + this.metadata + ); } @Override public InternalBucket createBucket(InternalAggregations aggregations, InternalBucket prototype) { - return new InternalBucket(prototype.docCount, aggregations, prototype.basename, prototype.termBytes, - prototype.level, prototype.minDepth, prototype.paths); + return new InternalBucket( + prototype.docCount, + aggregations, + prototype.basename, + prototype.termBytes, + prototype.level, + prototype.minDepth, + prototype.paths + ); } @Override @@ -258,7 +278,7 @@ public InternalPathHierarchy reduce(List aggregations, Redu final InternalBucket b = reduceBucket(sameTermBuckets, reduceContext); if (b.getDocCount() >= minDocCount || !reduceContext.isFinalReduce()) { reduceContext.consumeBucketsAndMaybeBreak(1); - String [] pathsForTree; + String[] pathsForTree; if (b.minDepth > 0) { pathsForTree = Arrays.copyOfRange(b.paths, b.minDepth, b.paths.length); } else { @@ -271,8 +291,17 @@ public InternalPathHierarchy reduce(List aggregations, Redu } long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; - return new InternalPathHierarchy(getName(), ordered.getAsList(), order, minDocCount, requiredSize, shardSize, - sum_other_hierarchy_nodes, separator, getMetadata()); + return new InternalPathHierarchy( + getName(), + ordered.getAsList(), + order, + minDocCount, + requiredSize, + shardSize, + sum_other_hierarchy_nodes, + separator, + getMetadata() + ); } /** @@ -296,7 +325,7 @@ protected InternalBucket reduceBucket(List buckets, ReduceContex @Override public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { -// builder.field(SUM_OF_OTHER_HIERARCHY_NODES.getPreferredName(), otherHierarchyNodes); + // builder.field(SUM_OF_OTHER_HIERARCHY_NODES.getPreferredName(), otherHierarchyNodes); Iterator bucketIterator = buckets.iterator(); builder.startArray(CommonFields.BUCKETS.getPreferredName()); InternalBucket prevBucket = null; @@ -323,14 +352,14 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th builder.startObject(); builder.field(CommonFields.KEY.getPreferredName(), currentBucket.basename); builder.field(CommonFields.DOC_COUNT.getPreferredName(), currentBucket.docCount); - builder.field(PATHS.getPreferredName(), Arrays.copyOf(currentBucket.paths, currentBucket.paths.length -1)); + builder.field(PATHS.getPreferredName(), Arrays.copyOf(currentBucket.paths, currentBucket.paths.length - 1)); currentBucket.getAggregations().toXContentInternal(builder, params); prevBucket = currentBucket; } if (currentBucket != null) { - for (int i=0; i < currentBucket.level; i++) { + for (int i = 0; i < currentBucket.level; i++) { builder.endObject(); builder.endArray(); builder.endObject(); @@ -351,11 +380,11 @@ public int hashCode() { public boolean equals(Object obj) { InternalPathHierarchy that = (InternalPathHierarchy) obj; return Objects.equals(buckets, that.buckets) - && Objects.equals(separator, that.separator) - && Objects.equals(order, that.order) - && Objects.equals(minDocCount, that.minDocCount) - && Objects.equals(requiredSize, that.requiredSize) - && Objects.equals(shardSize, that.shardSize) - && Objects.equals(otherHierarchyNodes, that.otherHierarchyNodes); + && Objects.equals(separator, that.separator) + && Objects.equals(order, that.order) + && Objects.equals(minDocCount, that.minDocCount) + && Objects.equals(requiredSize, that.requiredSize) + && Objects.equals(shardSize, that.shardSize) + && Objects.equals(otherHierarchyNodes, that.otherHierarchyNodes); } } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java index e134aa6..e497f8b 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java @@ -1,39 +1,38 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; import org.elasticsearch.Version; -import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xcontent.ObjectParser; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.XContentParser; -import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregatorFactories; -import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.AggregatorFactories.Builder; +import org.elasticsearch.search.aggregations.AggregatorFactory; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalOrder; +import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.xcontent.ObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; - /** * The builder of the aggregatorFactory. Also implements the parsing of the request. */ public class PathHierarchyAggregationBuilder extends ValuesSourceAggregationBuilder { public static final String NAME = "path_hierarchy"; public static final ValuesSourceRegistry.RegistryKey REGISTRY_KEY = - new ValuesSourceRegistry.RegistryKey<>(NAME, PathHierarchyAggregationSupplier.class); + new ValuesSourceRegistry.RegistryKey<>(NAME, PathHierarchyAggregationSupplier.class); public static final ParseField SEPARATOR_FIELD = new ParseField("separator"); public static final ParseField MIN_DEPTH_FIELD = new ParseField("min_depth"); @@ -45,10 +44,12 @@ public class PathHierarchyAggregationBuilder extends ValuesSourceAggregationBuil public static final ParseField SHARD_SIZE_FIELD = new ParseField("shard_size"); public static final ParseField MIN_DOC_COUNT_FIELD = new ParseField("min_doc_count"); - public static final PathHierarchyAggregator.BucketCountThresholds DEFAULT_BUCKET_COUNT_THRESHOLDS = new - PathHierarchyAggregator.BucketCountThresholds(10, -1); - public static final ObjectParser PARSER = - ObjectParser.fromBuilder(NAME, PathHierarchyAggregationBuilder::new); + public static final PathHierarchyAggregator.BucketCountThresholds DEFAULT_BUCKET_COUNT_THRESHOLDS = + new PathHierarchyAggregator.BucketCountThresholds(10, -1); + public static final ObjectParser PARSER = ObjectParser.fromBuilder( + NAME, + PathHierarchyAggregationBuilder::new + ); static { ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, false); @@ -60,8 +61,7 @@ public class PathHierarchyAggregationBuilder extends ValuesSourceAggregationBuil PARSER.declareInt(PathHierarchyAggregationBuilder::size, SIZE_FIELD); PARSER.declareLong(PathHierarchyAggregationBuilder::minDocCount, MIN_DOC_COUNT_FIELD); PARSER.declareInt(PathHierarchyAggregationBuilder::shardSize, SHARD_SIZE_FIELD); - PARSER.declareObjectArray(PathHierarchyAggregationBuilder::order, (p, c) -> InternalOrder.Parser.parseOrderParam(p), - ORDER_FIELD); + PARSER.declareObjectArray(PathHierarchyAggregationBuilder::order, (p, c) -> InternalOrder.Parser.parseOrderParam(p), ORDER_FIELD); } public static AggregationBuilder parse(String aggregationName, XContentParser parser) throws IOException { @@ -84,8 +84,8 @@ public static void registerAggregators(ValuesSourceRegistry.Builder builder) { private int depth = -1; private BucketOrder order = BucketOrder.compound(BucketOrder.count(false)); // automatically adds tie-breaker key asc order private PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new PathHierarchyAggregator.BucketCountThresholds( - DEFAULT_BUCKET_COUNT_THRESHOLDS); - + DEFAULT_BUCKET_COUNT_THRESHOLDS + ); private PathHierarchyAggregationBuilder(String name) { super(name); @@ -111,8 +111,7 @@ public PathHierarchyAggregationBuilder(StreamInput in) throws IOException { order = InternalOrder.Streams.readOrder(in); } - private PathHierarchyAggregationBuilder(PathHierarchyAggregationBuilder clone, Builder factoriesBuilder, - Map metadata) { + private PathHierarchyAggregationBuilder(PathHierarchyAggregationBuilder clone, Builder factoriesBuilder, Map metadata) { super(clone, factoriesBuilder, metadata); separator = clone.separator; minDepth = clone.minDepth; @@ -180,7 +179,7 @@ private PathHierarchyAggregationBuilder order(BucketOrder order) { if (order == null) { throw new IllegalArgumentException("[order] must not be null: [" + name + "]"); } - if(order instanceof InternalOrder.CompoundOrder || InternalOrder.isKeyOrder(order)) { + if (order instanceof InternalOrder.CompoundOrder || InternalOrder.isKeyOrder(order)) { this.order = order; // if order already contains a tie-breaker we are good to go } else { // otherwise add a tie-breaker by using a compound order this.order = BucketOrder.compound(order); @@ -197,7 +196,6 @@ private PathHierarchyAggregationBuilder order(List orders) { return this; } - /** * Sets the size - indicating how many term buckets should be returned * (defaults to 10) @@ -215,7 +213,8 @@ public PathHierarchyAggregationBuilder size(int size) { public PathHierarchyAggregationBuilder minDocCount(long minDocCount) { if (minDocCount < 0) { throw new IllegalArgumentException( - "[minDocCount] must be greater than or equal to 0. Found [" + minDocCount + "] in [" + name + "]"); + "[minDocCount] must be greater than or equal to 0. Found [" + minDocCount + "] in [" + name + "]" + ); } this.minDocCount = minDocCount; return this; @@ -241,8 +240,7 @@ public BucketCardinality bucketCardinality() { */ public PathHierarchyAggregationBuilder shardSize(int shardSize) { if (shardSize <= 0) { - throw new IllegalArgumentException( - "[shardSize] must be greater than 0. Found [" + shardSize + "] in [" + name + "]"); + throw new IllegalArgumentException("[shardSize] must be greater than 0. Found [" + shardSize + "] in [" + name + "]"); } bucketCountThresholds.setShardSize(shardSize); return this; @@ -256,38 +254,40 @@ public int shardSize() { } @Override - protected ValuesSourceAggregatorFactory innerBuild(AggregationContext context, - ValuesSourceConfig config, - AggregatorFactory parent, - AggregatorFactories.Builder subFactoriesBuilder) throws IOException { - + protected ValuesSourceAggregatorFactory innerBuild( + AggregationContext context, + ValuesSourceConfig config, + AggregatorFactory parent, + AggregatorFactories.Builder subFactoriesBuilder + ) throws IOException { - if (minDepth > maxDepth) - throw new IllegalArgumentException("[minDepth] (" + minDepth + ") must not be greater than [maxDepth] (" + - maxDepth + ")"); + if (minDepth > maxDepth) throw new IllegalArgumentException( + "[minDepth] (" + minDepth + ") must not be greater than [maxDepth] (" + maxDepth + ")" + ); if (depth >= 0) { - if (minDepth > depth) - throw new IllegalArgumentException("[minDepth] (" + minDepth + ") must not be greater than [depth] (" + - depth + ")"); + if (minDepth > depth) throw new IllegalArgumentException( + "[minDepth] (" + minDepth + ") must not be greater than [depth] (" + depth + ")" + ); minDepth = depth; maxDepth = depth; } return new PathHierarchyAggregatorFactory( - name, - config, - separator, - minDepth, - maxDepth, - keepBlankPath, - order, - minDocCount, - bucketCountThresholds, - context, - parent, - subFactoriesBuilder, - metadata); + name, + config, + separator, + minDepth, + maxDepth, + keepBlankPath, + order, + minDocCount, + bucketCountThresholds, + context, + parent, + subFactoriesBuilder, + metadata + ); } @Override @@ -332,12 +332,12 @@ public boolean equals(Object obj) { if (!super.equals(obj)) return false; PathHierarchyAggregationBuilder other = (PathHierarchyAggregationBuilder) obj; return Objects.equals(separator, other.separator) - && Objects.equals(minDepth, other.minDepth) - && Objects.equals(maxDepth, other.maxDepth) - && Objects.equals(depth, other.depth) - && Objects.equals(order, other.order) - && Objects.equals(minDocCount, other.minDocCount) - && Objects.equals(bucketCountThresholds, other.bucketCountThresholds); + && Objects.equals(minDepth, other.minDepth) + && Objects.equals(maxDepth, other.maxDepth) + && Objects.equals(depth, other.depth) + && Objects.equals(order, other.order) + && Objects.equals(minDocCount, other.minDocCount) + && Objects.equals(bucketCountThresholds, other.bucketCountThresholds); } @Override @@ -346,6 +346,7 @@ public String getType() { } @Override - protected ValuesSourceRegistry.RegistryKey getRegistryKey() { return REGISTRY_KEY; } + protected ValuesSourceRegistry.RegistryKey getRegistryKey() { + return REGISTRY_KEY; + } } - diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationSupplier.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationSupplier.java index 2f71ba7..8fa8c5b 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationSupplier.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationSupplier.java @@ -13,18 +13,20 @@ @FunctionalInterface public interface PathHierarchyAggregationSupplier { - Aggregator build(String name, - AggregatorFactories factories, - BytesRef separator, - int minDepth, - int maxDepth, - boolean keepBlankPath, - BucketOrder order, - long minDocCount, - PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds, - ValuesSourceConfig valuesSourceConfig, - SearchContext aggregationContext, - Aggregator parent, - CardinalityUpperBound cardinality, - Map metadata) throws IOException; + Aggregator build( + String name, + AggregatorFactories factories, + BytesRef separator, + int minDepth, + int maxDepth, + boolean keepBlankPath, + BucketOrder order, + long minDocCount, + PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds, + ValuesSourceConfig valuesSourceConfig, + SearchContext aggregationContext, + Aggregator parent, + CardinalityUpperBound cardinality, + Map metadata + ) throws IOException; } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java index aa6ef53..b99b8f2 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java @@ -7,10 +7,8 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.core.Releasables; import org.elasticsearch.common.util.BytesRefHash; -import org.elasticsearch.xcontent.ToXContentFragment; -import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.core.Releasables; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactories; @@ -20,8 +18,10 @@ import org.elasticsearch.search.aggregations.LeafBucketCollector; import org.elasticsearch.search.aggregations.LeafBucketCollectorBase; import org.elasticsearch.search.aggregations.bucket.BucketsAggregator; -import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; import java.util.Arrays; @@ -33,18 +33,19 @@ public class PathHierarchyAggregator extends BucketsAggregator { - public PathHierarchyAggregator(String name, - AggregatorFactories factories, - AggregationContext context, - ValuesSource valuesSource, - BucketOrder order, - long minDocCount, - BucketCountThresholds bucketCountThresholds, - BytesRef separator, - int minDepth, - Aggregator parent, - CardinalityUpperBound cardinality, - Map metadata + public PathHierarchyAggregator( + String name, + AggregatorFactories factories, + AggregationContext context, + ValuesSource valuesSource, + BucketOrder order, + long minDocCount, + BucketCountThresholds bucketCountThresholds, + BytesRef separator, + int minDepth, + Aggregator parent, + CardinalityUpperBound cardinality, + Map metadata ) throws IOException { super(name, factories, context, parent, cardinality, metadata); this.valuesSource = valuesSource; @@ -135,12 +136,10 @@ public boolean equals(Object obj) { return false; } PathHierarchyAggregator.BucketCountThresholds other = (PathHierarchyAggregator.BucketCountThresholds) obj; - return Objects.equals(requiredSize, other.requiredSize) - && Objects.equals(shardSize, other.shardSize); + return Objects.equals(requiredSize, other.requiredSize) && Objects.equals(shardSize, other.shardSize); } } - private final ValuesSource valuesSource; private final BytesRefHash bucketOrds; private final BucketOrder order; @@ -164,6 +163,7 @@ public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCol final SortedBinaryDocValues values = valuesSource.bytesValues(ctx); return new LeafBucketCollectorBase(sub, values) { final BytesRefBuilder previous = new BytesRefBuilder(); + /** * Collect the given doc in the given bucket. * Called once for every document matching a query, with the unbased document number. @@ -183,7 +183,7 @@ public void collect(int doc, long owningBucketOrdinal) throws IOException { } long bucketOrdinal = bucketOrds.add(bytesValue); if (bucketOrdinal < 0) { // already seen - bucketOrdinal = - 1 - bucketOrdinal; + bucketOrdinal = -1 - bucketOrdinal; collectExistingBucket(sub, doc, bucketOrdinal); } else { collectBucket(sub, doc, bucketOrdinal); @@ -205,8 +205,10 @@ public InternalAggregation[] buildAggregations(long[] owningBucketOrdinals) thro assert owningBucketOrdinals[ordIdx] == 0; final int size = (int) Math.min(bucketOrds.size(), bucketCountThresholds.getShardSize()); - PathSortedTree pathSortedTree = - new PathSortedTree<>(partiallyBuiltBucketComparator, size); + PathSortedTree pathSortedTree = new PathSortedTree<>( + partiallyBuiltBucketComparator, + size + ); InternalPathHierarchy.InternalBucket spare; for (int i = 0; i < bucketOrds.size(); i++) { @@ -247,25 +249,38 @@ public InternalAggregation[] buildAggregations(long[] owningBucketOrdinals) thro otherHierarchyNodes -= 1; } - results[ordIdx] = new InternalPathHierarchy(name, Arrays.asList(topBucketsPerOrd[ordIdx]), order, - minDocCount, bucketCountThresholds.getRequiredSize(), bucketCountThresholds.getShardSize(), - otherHierarchyNodes, separator, metadata()); + results[ordIdx] = new InternalPathHierarchy( + name, + Arrays.asList(topBucketsPerOrd[ordIdx]), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + otherHierarchyNodes, + separator, + metadata() + ); } // Build sub-aggregations for pruned buckets - buildSubAggsForAllBuckets( - topBucketsPerOrd, - b -> b.bucketOrd, - (b, aggregations) -> b.aggregations = aggregations - ); + buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); return results; } @Override public InternalAggregation buildEmptyAggregation() { - return new InternalPathHierarchy(name, null, order, minDocCount, bucketCountThresholds.getRequiredSize(), - bucketCountThresholds.getShardSize(), 0, separator, metadata()); + return new InternalPathHierarchy( + name, + null, + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + 0, + separator, + metadata() + ); } @Override diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java index 0c383f9..103c461 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java @@ -17,11 +17,11 @@ import org.elasticsearch.search.aggregations.NonCollectingAggregator; import org.elasticsearch.search.aggregations.bucket.BucketUtils; import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceAggregatorFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; -import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; import java.io.IOException; import java.util.ArrayList; @@ -41,19 +41,20 @@ class PathHierarchyAggregatorFactory extends ValuesSourceAggregatorFactory { private boolean keepBlankPath; private final PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds; - PathHierarchyAggregatorFactory(String name, - ValuesSourceConfig config, - String separator, - int minDepth, - int maxDepth, - boolean keepBlankPath, - BucketOrder order, - long minDocCount, - PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds, - AggregationContext context, - AggregatorFactory parent, - AggregatorFactories.Builder subFactoriesBuilder, - Map metaData + PathHierarchyAggregatorFactory( + String name, + ValuesSourceConfig config, + String separator, + int minDepth, + int maxDepth, + boolean keepBlankPath, + BucketOrder order, + long minDocCount, + PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds, + AggregationContext context, + AggregatorFactory parent, + AggregatorFactories.Builder subFactoriesBuilder, + Map metaData ) throws IOException { super(name, config, context, parent, subFactoriesBuilder, metaData); this.separator = new BytesRef(separator); @@ -66,27 +67,41 @@ class PathHierarchyAggregatorFactory extends ValuesSourceAggregatorFactory { } public static void registerAggregators(ValuesSourceRegistry.Builder builder) { - builder.register(PathHierarchyAggregationBuilder.REGISTRY_KEY, CoreValuesSourceType.KEYWORD, (name, - factories, - separator, - minDepth, - maxDepth, - keepBlankPath, - order, - minDocCount, - bucketCountThresholds, - valuesSourceConfig, - aggregationContext, - parent, - cardinality, - metadata) -> null, - true); + builder.register( + PathHierarchyAggregationBuilder.REGISTRY_KEY, + CoreValuesSourceType.KEYWORD, + ( + name, + factories, + separator, + minDepth, + maxDepth, + keepBlankPath, + order, + minDocCount, + bucketCountThresholds, + valuesSourceConfig, + aggregationContext, + parent, + cardinality, + metadata) -> null, + true + ); } @Override protected Aggregator createUnmapped(Aggregator parent, Map metadata) throws IOException { - final InternalAggregation aggregation = new InternalPathHierarchy(name, new ArrayList<>(), order, minDocCount, - bucketCountThresholds.getRequiredSize(), bucketCountThresholds.getShardSize(), 0, separator, metadata); + final InternalAggregation aggregation = new InternalPathHierarchy( + name, + new ArrayList<>(), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + 0, + separator, + metadata + ); return new NonCollectingAggregator(name, context, parent, factories, metadata) { { // even in the case of an unmapped aggregator, validate the @@ -102,13 +117,14 @@ public InternalAggregation buildEmptyAggregation() { } @Override - protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound cardinality, - Map metadata) throws IOException { + protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound cardinality, Map metadata) + throws IOException { ValuesSource valuesSourceBytes = new HierarchyValuesSource(config.getValuesSource(), separator, minDepth, maxDepth, keepBlankPath); - PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new - PathHierarchyAggregator.BucketCountThresholds(this.bucketCountThresholds); + PathHierarchyAggregator.BucketCountThresholds bucketCountThresholds = new PathHierarchyAggregator.BucketCountThresholds( + this.bucketCountThresholds + ); if (!InternalOrder.isKeyOrder(order) - && bucketCountThresholds.getShardSize() == PathHierarchyAggregationBuilder.DEFAULT_BUCKET_COUNT_THRESHOLDS.getShardSize()) { + && bucketCountThresholds.getShardSize() == PathHierarchyAggregationBuilder.DEFAULT_BUCKET_COUNT_THRESHOLDS.getShardSize()) { // The user has not made a shardSize selection. Use default // heuristic to avoid any wrong-ranking caused by distributed // counting @@ -116,9 +132,19 @@ protected Aggregator doCreateInternal(Aggregator parent, CardinalityUpperBound c } bucketCountThresholds.ensureValidity(); return new PathHierarchyAggregator( - name, factories, context, - valuesSourceBytes, order, minDocCount, bucketCountThresholds, separator, minDepth, - parent, cardinality, metadata); + name, + factories, + context, + valuesSourceBytes, + order, + minDocCount, + bucketCountThresholds, + separator, + minDepth, + parent, + cardinality, + metadata + ); } /** @@ -137,8 +163,7 @@ private static class HierarchyValues extends SortingBinaryDocValues { private int maxDepth; private boolean keepBlankPath; - private HierarchyValues(SortedBinaryDocValues valuesSource, BytesRef separator, int minDepth, int maxDepth, - boolean keepBlankPath) { + private HierarchyValues(SortedBinaryDocValues valuesSource, BytesRef separator, int minDepth, int maxDepth, boolean keepBlankPath) { this.valuesSource = valuesSource; this.separator = separator; this.minDepth = minDepth; @@ -157,21 +182,26 @@ public boolean advanceExact(int docId) throws IOException { if (valuesSource.advanceExact(docId)) { count = 0; int t = 0; - for (int i=0; i < valuesSource.docValueCount(); i++) { + for (int i = 0; i < valuesSource.docValueCount(); i++) { int depth = 0; BytesRef val = valuesSource.nextValue(); BytesRefBuilder cleanVal = new BytesRefBuilder(); int startNewValOffset = -1; - for (int offset=0; offset < val.length; offset++) { + for (int offset = 0; offset < val.length; offset++) { // it is a separator - if (val.length - offset >= separator.length && - FutureArrays.equals( - separator.bytes, separator.offset, separator.offset + separator.length, - val.bytes, val.offset + offset, val.offset + offset + separator.length)) { + if (val.length - offset >= separator.length + && FutureArrays.equals( + separator.bytes, + separator.offset, + separator.offset + separator.length, + val.bytes, + val.offset + offset, + val.offset + offset + separator.length + )) { // ignore separator at the beginning if (offset == 0) { - offset += separator.length -1; + offset += separator.length - 1; continue; } @@ -183,14 +213,14 @@ public boolean advanceExact(int docId) throws IOException { } startNewValOffset = -1; cleanVal.append(separator); - depth ++; - // two separators following each other + depth++; + // two separators following each other } else if (keepBlankPath) { count++; growExact(); values[t++].copyBytes(cleanVal); cleanVal.append(separator); - depth ++; + depth++; } if (maxDepth >= 0 && depth > maxDepth) { @@ -216,8 +246,7 @@ public boolean advanceExact(int docId) throws IOException { } sort(); // sort values that are stored between offsets 0 and count of values return true; - } else - return false; + } else return false; } final void growExact() { @@ -241,7 +270,7 @@ private static class HierarchyValuesSource extends ValuesSource.Bytes { private final int maxDepth; private final boolean twoSepAsOne; - private HierarchyValuesSource(ValuesSource values, BytesRef separator, int minDepth, int maxDepth, boolean twoSepAsOne){ + private HierarchyValuesSource(ValuesSource values, BytesRef separator, int minDepth, int maxDepth, boolean twoSepAsOne) { this.values = values; this.separator = separator; this.minDepth = minDepth; @@ -256,4 +285,3 @@ public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOExc } } - diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathSortedTree.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathSortedTree.java index f9d7e76..3842a72 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathSortedTree.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathSortedTree.java @@ -1,6 +1,5 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; - import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; @@ -9,7 +8,7 @@ import java.util.PriorityQueue; import java.util.Stack; -public class PathSortedTree implements Iterable{ +public class PathSortedTree implements Iterable { private Comparator comparator; private Node root; @@ -45,20 +44,19 @@ public void add(K[] path, T element) { if (newChild) { Node newNode = new Node<>(k, comparator, element, currentNode); currentNode.children.add(newNode); - fullSize ++; + fullSize++; break; } } } - public List getAsList() { List result = new ArrayList<>(fullSize); Iterator iterator = consumer(); - while (iterator.hasNext()){ + while (iterator.hasNext()) { result.add(iterator.next()); } return result; @@ -68,7 +66,6 @@ public Iterator consumer() { return new PathSortedTreeConsumer(root, fullSize); } - @Override public Iterator iterator() { return new PathSortedTreeIterator(root); @@ -93,7 +90,6 @@ Comparator> getComparator(Comparator comparator) { return (n1, n2) -> comparator.compare(n1.data, n2.data); } - public Node(K key, Comparator comparator, T data, Node parent) { this.key = key; this.data = data; @@ -122,11 +118,11 @@ public T next() { Node nextNode = current.next(); - if (! nextNode.children.isEmpty()) { + if (!nextNode.children.isEmpty()) { iterators.push(current); current = nextNode.children.iterator(); - } else if (! current.hasNext()){ - while (! iterators.empty()) { + } else if (!current.hasNext()) { + while (!iterators.empty()) { current = iterators.pop(); if (current.hasNext()) { break; @@ -153,7 +149,7 @@ private class PathSortedTreeConsumer implements Iterator { @Override public boolean hasNext() { - if (size >=0 && currentSize >= size) { + if (size >= 0 && currentSize >= size) { return false; } if (cursor.children.size() > 0) { @@ -177,8 +173,8 @@ public T next() { } } if (nextNode == null) throw new NoSuchElementException(); - currentSize ++; - fullSize --; + currentSize++; + fullSize--; cursor = nextNode; return nextNode.data; diff --git a/src/test/java/org/opendatasoft/elasticsearch/PathHierarchyTests.java b/src/test/java/org/opendatasoft/elasticsearch/PathHierarchyTests.java index 7ee12d0..01f905e 100644 --- a/src/test/java/org/opendatasoft/elasticsearch/PathHierarchyTests.java +++ b/src/test/java/org/opendatasoft/elasticsearch/PathHierarchyTests.java @@ -1,28 +1,28 @@ package org.opendatasoft.elasticsearch; -import org.elasticsearch.xcontent.XContentParser; -import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.json.JsonXContent; import org.opendatasoft.elasticsearch.search.aggregations.bucket.PathHierarchyAggregationBuilder; public class PathHierarchyTests extends ESTestCase { public void testParser() throws Exception { // can create the factory with utf8 separator String separator = "夢"; - XContentParser stParser = createParser(JsonXContent.jsonXContent, - "{\"field\":\"path\", \"separator\": \"" + separator + "\"}"); + XContentParser stParser = createParser(JsonXContent.jsonXContent, "{\"field\":\"path\", \"separator\": \"" + separator + "\"}"); XContentParser.Token token = stParser.nextToken(); assertSame(XContentParser.Token.START_OBJECT, token); assertNotNull(PathHierarchyAggregationBuilder.parse("path_hierarchy", stParser)); // can create the factory with an array of orders String orders = "[{\"_key\": \"asc\"}, {\"_count\": \"desc\"}]"; - stParser = createParser(JsonXContent.jsonXContent, - "{\"field\":\"path\", \"order\": " + orders + "}"); + stParser = createParser(JsonXContent.jsonXContent, "{\"field\":\"path\", \"order\": " + orders + "}"); assertNotNull(PathHierarchyAggregationBuilder.parse("path_hierarchy", stParser)); - stParser = createParser(JsonXContent.jsonXContent, - "{\"field\":\"path\", \"separator\":\"/\", \"order\": " + orders + ", \"min_depth\": 0, \"max_depth\": 3}"); + stParser = createParser( + JsonXContent.jsonXContent, + "{\"field\":\"path\", \"separator\":\"/\", \"order\": " + orders + ", \"min_depth\": 0, \"max_depth\": 3}" + ); AggregationBuilder builder = PathHierarchyAggregationBuilder.parse("path_hierarchy", stParser); assertNotNull(builder); } diff --git a/src/yamlRestTest/java/org/opendatasoft/elasticsearch/RestApiYamlIT.java b/src/yamlRestTest/java/org/opendatasoft/elasticsearch/RestApiYamlIT.java index b454a26..2939acd 100644 --- a/src/yamlRestTest/java/org/opendatasoft/elasticsearch/RestApiYamlIT.java +++ b/src/yamlRestTest/java/org/opendatasoft/elasticsearch/RestApiYamlIT.java @@ -2,6 +2,7 @@ import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; @@ -10,7 +11,7 @@ */ public class RestApiYamlIT extends ESClientYamlSuiteTestCase { - public RestApiYamlIT (@Name("yaml") ClientYamlTestCandidate testCandidate) { + public RestApiYamlIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } From 513811e608030d6ade5f874a1850716168c4ff2e Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Fri, 20 Jun 2025 11:56:03 +0200 Subject: [PATCH 02/15] update the elasticsearch version to 8.18.1 --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index ead8d50..5042b70 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -es_version = 7.17.28 -plugin_version = 7.17.28.0 +es_version = 8.18.1 +plugin_version = 8.18.1.0 From 7d71255ed7e0034861a6e430194f6246d9e355e8 Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Fri, 20 Jun 2025 11:59:48 +0200 Subject: [PATCH 03/15] Adapt the plugin to Elastic 8 Thanks to @nicenatalia Co-authored-by: Natalia Escalera --- .../DateHierarchyAggregationBuilder.java | 19 ++- .../bucket/DateHierarchyAggregator.java | 115 +++++++------ .../bucket/InternalDateHierarchy.java | 123 +++++++------- .../bucket/InternalPathHierarchy.java | 136 ++++++++-------- .../PathHierarchyAggregationBuilder.java | 15 +- .../bucket/PathHierarchyAggregator.java | 154 ++++++++++-------- .../PathHierarchyAggregatorFactory.java | 4 +- 7 files changed, 305 insertions(+), 261 deletions(-) diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java index 7bd454b..402337d 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java @@ -1,6 +1,7 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; -import org.elasticsearch.Version; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.Rounding; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -40,6 +41,11 @@ * The builder of the aggregatorFactory. Also implements the parsing of the request. */ public class DateHierarchyAggregationBuilder extends ValuesSourceAggregationBuilder { + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.V_8_0_0; + } + public static final String NAME = "date_hierarchy"; public static final ValuesSourceRegistry.RegistryKey REGISTRY_KEY = new ValuesSourceRegistry.RegistryKey<>(NAME, DateHierarchyAggregationSupplier.class); @@ -140,8 +146,8 @@ public void writeTo(StreamOutput out) throws IOException { DateHierarchyAggregationBuilder::new ); static { - - ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, true); + // ES 8.x introduces field validation. Setting timezoneAware to false to avoid duplication of the timezone field + ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, false); PARSER.declareString(DateHierarchyAggregationBuilder::interval, INTERVAL_FIELD); @@ -180,7 +186,7 @@ private DateHierarchyAggregationBuilder(String name) { } @Override - protected boolean serializeTargetValueType(Version version) { + protected boolean serializeTargetValueType(TransportVersion version) { return true; } @@ -412,9 +418,4 @@ public boolean equals(Object obj) { public String getType() { return NAME; } - - @Override - protected ValuesSourceRegistry.RegistryKey getRegistryKey() { - return REGISTRY_KEY; - } } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java index e993d5a..adafdc1 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java @@ -1,15 +1,16 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; -import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.Rounding; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.util.BytesRefHash; +import org.elasticsearch.common.util.LongArray; +import org.elasticsearch.common.util.ObjectArray; import org.elasticsearch.core.Releasables; +import org.elasticsearch.search.aggregations.AggregationExecutionContext; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.BucketOrder; @@ -25,7 +26,6 @@ import java.io.IOException; import java.util.Arrays; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -54,7 +54,6 @@ public DateHierarchyAggregator( this.bucketCountThresholds = bucketCountThresholds; order.validate(this); this.order = order; - this.partiallyBuiltBucketComparator = order == null ? null : order.partiallyBuiltBucketComparator(b -> b.bucketOrd, this); } public static class BucketCountThresholds implements Writeable, ToXContentFragment { @@ -144,7 +143,6 @@ public boolean equals(Object obj) { private final long minDocCount; private final BucketCountThresholds bucketCountThresholds; private final List preparedRoundings; - protected final Comparator partiallyBuiltBucketComparator; /** * The collector collects the docs, including or not some score (depending of the including of a Scorer) in the @@ -153,11 +151,11 @@ public boolean equals(Object obj) { * The LeafBucketCollector is a "Per-leaf bucket collector". It collects docs for the account of buckets. */ @Override - public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { + public LeafBucketCollector getLeafCollector(AggregationExecutionContext ctx, LeafBucketCollector sub) throws IOException { if (valuesSource == null) { return LeafBucketCollector.NO_OP_COLLECTOR; } - final SortedNumericDocValues values = valuesSource.longValues(ctx); + final SortedNumericDocValues values = valuesSource.longValues(ctx.getLeafReaderContext()); return new LeafBucketCollectorBase(sub, values) { @@ -189,63 +187,76 @@ public void collect(int doc, long bucket) throws IOException { } @Override - public InternalAggregation[] buildAggregations(long[] owningBucketOrdinals) throws IOException { + public InternalAggregation[] buildAggregations(LongArray owningBucketOrdinals) throws IOException { - InternalDateHierarchy.InternalBucket[][] topBucketsPerOrd = new InternalDateHierarchy.InternalBucket[owningBucketOrdinals.length][]; - InternalDateHierarchy[] results = new InternalDateHierarchy[owningBucketOrdinals.length]; + // InternalDateHierarchy.InternalBucket[][] topBucketsPerOrd = new + // InternalDateHierarchy.InternalBucket[owningBucketOrdinals.length][]; + // InternalDateHierarchy[] results = new InternalDateHierarchy[owningBucketOrdinals.length]; - for (int ordIdx = 0; ordIdx < owningBucketOrdinals.length; ordIdx++) { - assert owningBucketOrdinals[ordIdx] == 0; + try ( + ObjectArray topBucketsPerOrd = bigArrays().newObjectArray(owningBucketOrdinals.size()) + ) { - // build buckets and store them sorted - final int size = (int) Math.min(bucketOrds.size(), bucketCountThresholds.getShardSize()); + InternalDateHierarchy[] results = new InternalDateHierarchy[Math.toIntExact(owningBucketOrdinals.size())]; - PathSortedTree pathSortedTree = new PathSortedTree<>(order.comparator(), size); + for (int ordIdx = 0; ordIdx < owningBucketOrdinals.size(); ordIdx++) { + // assert owningBucketOrdinals[ordIdx] == 0; + assert owningBucketOrdinals.get(ordIdx) == 0; - InternalDateHierarchy.InternalBucket spare; - for (int i = 0; i < bucketOrds.size(); i++) { - spare = new InternalDateHierarchy.InternalBucket(0, null, null, null, 0, null); + // build buckets and store them sorted + final int size = (int) Math.min(bucketOrds.size(), bucketCountThresholds.getShardSize()); - BytesRef term = new BytesRef(); - bucketOrds.get(i, term); - String[] paths = term.utf8ToString().split("/", -1); + PathSortedTree pathSortedTree = new PathSortedTree<>( + order.comparator(), + size + ); - spare.paths = paths; - spare.key = term; - spare.level = paths.length - 1; - spare.name = paths[spare.level]; - spare.docCount = bucketDocCount(i); - spare.bucketOrd = i; + InternalDateHierarchy.InternalBucket spare; + for (int i = 0; i < bucketOrds.size(); i++) { + spare = new InternalDateHierarchy.InternalBucket(0, null, null, null, 0, null); - pathSortedTree.add(spare.paths, spare); - } + BytesRef term = new BytesRef(); + bucketOrds.get(i, term); + String[] paths = term.utf8ToString().split("/", -1); - // Get the top buckets - topBucketsPerOrd[ordIdx] = new InternalDateHierarchy.InternalBucket[size]; - long otherHierarchyNodes = pathSortedTree.getFullSize(); - Iterator iterator = pathSortedTree.consumer(); - for (int i = 0; i < size; i++) { - final InternalDateHierarchy.InternalBucket bucket = iterator.next(); - topBucketsPerOrd[ordIdx][i] = bucket; - otherHierarchyNodes -= 1; - } + spare.paths = paths; + spare.key = term; + spare.level = paths.length - 1; + spare.name = paths[spare.level]; + spare.docCount = bucketDocCount(i); + spare.bucketOrd = i; - results[ordIdx] = new InternalDateHierarchy( - name, - Arrays.asList(topBucketsPerOrd[ordIdx]), - order, - minDocCount, - bucketCountThresholds.getRequiredSize(), - bucketCountThresholds.getShardSize(), - otherHierarchyNodes, - metadata() - ); - } + pathSortedTree.add(spare.paths, spare); + } - // Build sub-aggregations for pruned buckets - buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); + // Get the top buckets + topBucketsPerOrd.set(ordIdx, new InternalDateHierarchy.InternalBucket[size]); + // topBucketsPerOrd[ordIdx] = new InternalDateHierarchy.InternalBucket[size]; + long otherHierarchyNodes = pathSortedTree.getFullSize(); + Iterator iterator = pathSortedTree.consumer(); + for (int i = 0; i < size; i++) { + final InternalDateHierarchy.InternalBucket bucket = iterator.next(); + topBucketsPerOrd.get(ordIdx)[i] = bucket; + otherHierarchyNodes -= 1; + } - return results; + results[ordIdx] = new InternalDateHierarchy( + name, + Arrays.asList(topBucketsPerOrd.get(ordIdx)), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + otherHierarchyNodes, + metadata() + ); + } + + // Build sub-aggregations for pruned buckets + buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); + + return results; + } } @Override diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java index 835797b..f1cdee7 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalDateHierarchy.java @@ -3,7 +3,8 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.AggregationReduceContext; +import org.elasticsearch.search.aggregations.AggregatorReducer; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregations; @@ -15,11 +16,13 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.TreeMap; /** * An internal implementation of {@link InternalMultiBucketAggregation} @@ -28,11 +31,67 @@ */ public class InternalDateHierarchy extends InternalMultiBucketAggregation { + @Override + protected AggregatorReducer getLeaderReducer(AggregationReduceContext reduceContext, int size) { + Map> buckets = new LinkedHashMap<>(); + + return new AggregatorReducer() { + private long otherHierarchyNodes = 0; + + @Override + public void accept(InternalAggregation aggregation) { + InternalDateHierarchy dateHierarchy = (InternalDateHierarchy) aggregation; + + otherHierarchyNodes += dateHierarchy.getSumOtherHierarchyNodes(); + + for (InternalBucket bucket : dateHierarchy.buckets) { + List existingBuckets = buckets.get(bucket.key); + if (existingBuckets == null) { + existingBuckets = new ArrayList<>(size); + buckets.put(bucket.key, existingBuckets); + } + existingBuckets.add(bucket); + } + } + + @Override + public InternalAggregation get() { + final int size = !reduceContext.isFinalReduce() ? buckets.size() : Math.min(requiredSize, buckets.size()); + PathSortedTree ordered = new PathSortedTree<>(order.comparator(), size); + + for (List sameTermBuckets : buckets.values()) { + final InternalBucket b = reduceBucket(sameTermBuckets, reduceContext); + if (b.getDocCount() >= minDocCount || !reduceContext.isFinalReduce()) { + reduceContext.consumeBucketsAndMaybeBreak(1); + ordered.add(b.paths, b); + } else { + reduceContext.consumeBucketsAndMaybeBreak(-countInnerBucket(b)); + } + } + + long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; + + return new InternalDateHierarchy( + getName(), + ordered.getAsList(), + order, + minDocCount, + requiredSize, + shardSize, + sum_other_hierarchy_nodes, + getMetadata() + ); + } + }; + } + /** * The bucket class of InternalDateHierarchy. * @see MultiBucketsAggregation.Bucket */ - public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucket implements KeyComparable { + public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucketWritable + implements + KeyComparable { BytesRef key; String name; @@ -104,11 +163,10 @@ public long getDocCount() { } @Override - public Aggregations getAggregations() { + public InternalAggregations getAggregations() { return aggregations; } - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field(CommonFields.DOC_COUNT.getPreferredName(), docCount); @@ -214,62 +272,7 @@ public List getBuckets() { return buckets; } - /** - * Reduces the given aggregations to a single one and returns it. - */ - @Override - public InternalDateHierarchy reduce(List aggregations, ReduceContext reduceContext) { - Map> buckets = null; - long otherHierarchyNodes = 0; - - // extract buckets from aggregations - for (InternalAggregation aggregation : aggregations) { - InternalDateHierarchy dateHierarchy = (InternalDateHierarchy) aggregation; - if (buckets == null) { - buckets = new LinkedHashMap<>(); - } - - otherHierarchyNodes += dateHierarchy.getSumOtherHierarchyNodes(); - - for (InternalBucket bucket : dateHierarchy.buckets) { - List existingBuckets = buckets.get(bucket.key); - if (existingBuckets == null) { - existingBuckets = new ArrayList<>(aggregations.size()); - buckets.put(bucket.key, existingBuckets); - } - existingBuckets.add(bucket); - } - } - - // reduce and sort buckets depending of ordering rules - final int size = !reduceContext.isFinalReduce() ? buckets.size() : Math.min(requiredSize, buckets.size()); - PathSortedTree ordered = new PathSortedTree<>(order.comparator(), size); - for (List sameTermBuckets : buckets.values()) { - - final InternalBucket b = reduceBucket(sameTermBuckets, reduceContext); - if (b.getDocCount() >= minDocCount || !reduceContext.isFinalReduce()) { - reduceContext.consumeBucketsAndMaybeBreak(1); - ordered.add(b.paths, b); - } else { - reduceContext.consumeBucketsAndMaybeBreak(-countInnerBucket(b)); - } - } - - long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; - return new InternalDateHierarchy( - getName(), - ordered.getAsList(), - order, - minDocCount, - requiredSize, - shardSize, - sum_other_hierarchy_nodes, - getMetadata() - ); - } - - @Override - protected InternalBucket reduceBucket(List buckets, ReduceContext context) { + protected InternalBucket reduceBucket(List buckets, AggregationReduceContext context) { List aggregationsList = new ArrayList<>(buckets.size()); InternalBucket reduced = null; for (InternalBucket bucket : buckets) { diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java index e35e98e..29884d5 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java @@ -4,7 +4,8 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.search.aggregations.Aggregation; -import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.AggregationReduceContext; +import org.elasticsearch.search.aggregations.AggregatorReducer; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.InternalAggregations; @@ -32,11 +33,76 @@ public class InternalPathHierarchy extends InternalMultiBucketAggregation> buckets = new TreeMap<>(); + + return new AggregatorReducer() { + // Need a global otherHierarchyNodes counter that is increased in accept() and used in get() + private long otherHierarchyNodes = 0; + + @Override + public void accept(InternalAggregation aggregation) { + InternalPathHierarchy pathHierarchy = (InternalPathHierarchy) aggregation; + otherHierarchyNodes += pathHierarchy.getSumOtherHierarchyNodes(); + + for (InternalBucket bucket : pathHierarchy.buckets) { + List existingBuckets = buckets.get(bucket.termBytes); + if (existingBuckets == null) { + existingBuckets = new ArrayList<>(size); + buckets.put(bucket.termBytes, existingBuckets); + } + existingBuckets.add(bucket); + } + } + + @Override + public InternalAggregation get() { + // reduce and sort buckets depending of ordering rules + final int size = !reduceContext.isFinalReduce() ? buckets.size() : Math.min(requiredSize, buckets.size()); + PathSortedTree ordered = new PathSortedTree<>(order.comparator(), size); + + for (List sameTermBuckets : buckets.values()) { + final InternalBucket b = reduceBucket(sameTermBuckets, reduceContext); + if (b.getDocCount() >= minDocCount || !reduceContext.isFinalReduce()) { + reduceContext.consumeBucketsAndMaybeBreak(1); + String[] pathsForTree; + if (b.minDepth > 0) { + pathsForTree = Arrays.copyOfRange(b.paths, b.minDepth, b.paths.length); + } else { + pathsForTree = b.paths; + } + ordered.add(pathsForTree, b); + } else { + reduceContext.consumeBucketsAndMaybeBreak(-countInnerBucket(b)); + } + } + + long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; + + return new InternalPathHierarchy( + getName(), + ordered.getAsList(), + order, + minDocCount, + requiredSize, + shardSize, + sum_other_hierarchy_nodes, + separator, + getMetadata() + ); + + } + }; + } + /** * The bucket class of InternalPathHierarchy. * @see MultiBucketsAggregation.Bucket */ - public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucket implements KeyComparable { + public static class InternalBucket extends InternalMultiBucketAggregation.InternalBucketWritable + implements + KeyComparable { BytesRef termBytes; long bucketOrd; @@ -120,11 +186,10 @@ public long getDocCount() { } @Override - public Aggregations getAggregations() { + public InternalAggregations getAggregations() { return aggregations; } - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field(CommonFields.DOC_COUNT.getPreferredName(), docCount); @@ -244,71 +309,10 @@ public List getBuckets() { return buckets; } - /** - * Reduces the given aggregations to a single one and returns it. - */ - @Override - public InternalPathHierarchy reduce(List aggregations, ReduceContext reduceContext) { - Map> buckets = null; - long otherHierarchyNodes = 0; - - // extract buckets from aggregations - for (InternalAggregation aggregation : aggregations) { - InternalPathHierarchy pathHierarchy = (InternalPathHierarchy) aggregation; - if (buckets == null) { - buckets = new TreeMap<>(); - } - - otherHierarchyNodes += pathHierarchy.getSumOtherHierarchyNodes(); - - for (InternalBucket bucket : pathHierarchy.buckets) { - List existingBuckets = buckets.get(bucket.termBytes); - if (existingBuckets == null) { - existingBuckets = new ArrayList<>(aggregations.size()); - buckets.put(bucket.termBytes, existingBuckets); - } - existingBuckets.add(bucket); - } - } - - // reduce and sort buckets depending of ordering rules - final int size = !reduceContext.isFinalReduce() ? buckets.size() : Math.min(requiredSize, buckets.size()); - PathSortedTree ordered = new PathSortedTree<>(order.comparator(), size); - for (List sameTermBuckets : buckets.values()) { - final InternalBucket b = reduceBucket(sameTermBuckets, reduceContext); - if (b.getDocCount() >= minDocCount || !reduceContext.isFinalReduce()) { - reduceContext.consumeBucketsAndMaybeBreak(1); - String[] pathsForTree; - if (b.minDepth > 0) { - pathsForTree = Arrays.copyOfRange(b.paths, b.minDepth, b.paths.length); - } else { - pathsForTree = b.paths; - } - ordered.add(pathsForTree, b); - } else { - reduceContext.consumeBucketsAndMaybeBreak(-countInnerBucket(b)); - } - } - - long sum_other_hierarchy_nodes = ordered.getFullSize() - size + otherHierarchyNodes; - return new InternalPathHierarchy( - getName(), - ordered.getAsList(), - order, - minDocCount, - requiredSize, - shardSize, - sum_other_hierarchy_nodes, - separator, - getMetadata() - ); - } - /** * Utility method of InternalPathHierarchy.doReduce() */ - @Override - protected InternalBucket reduceBucket(List buckets, ReduceContext context) { + protected InternalBucket reduceBucket(List buckets, AggregationReduceContext context) { List aggregationsList = new ArrayList<>(buckets.size()); InternalBucket reduced = null; for (InternalBucket bucket : buckets) { diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java index e497f8b..eb2a32e 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregationBuilder.java @@ -1,6 +1,7 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; -import org.elasticsearch.Version; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.search.aggregations.AggregationBuilder; @@ -30,6 +31,11 @@ * The builder of the aggregatorFactory. Also implements the parsing of the request. */ public class PathHierarchyAggregationBuilder extends ValuesSourceAggregationBuilder { + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.V_8_0_0; + } + public static final String NAME = "path_hierarchy"; public static final ValuesSourceRegistry.RegistryKey REGISTRY_KEY = new ValuesSourceRegistry.RegistryKey<>(NAME, PathHierarchyAggregationSupplier.class); @@ -92,7 +98,7 @@ private PathHierarchyAggregationBuilder(String name) { } @Override - protected boolean serializeTargetValueType(Version version) { + protected boolean serializeTargetValueType(TransportVersion version) { return true; } @@ -344,9 +350,4 @@ public boolean equals(Object obj) { public String getType() { return NAME; } - - @Override - protected ValuesSourceRegistry.RegistryKey getRegistryKey() { - return REGISTRY_KEY; - } } diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java index b99b8f2..9655692 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregator.java @@ -1,6 +1,5 @@ package org.opendatasoft.elasticsearch.search.aggregations.bucket; -import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; import org.elasticsearch.ElasticsearchException; @@ -8,8 +7,11 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.util.BytesRefHash; +import org.elasticsearch.common.util.LongArray; +import org.elasticsearch.common.util.ObjectArray; import org.elasticsearch.core.Releasables; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; +import org.elasticsearch.search.aggregations.AggregationExecutionContext; import org.elasticsearch.search.aggregations.Aggregator; import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.BucketOrder; @@ -18,6 +20,7 @@ import org.elasticsearch.search.aggregations.LeafBucketCollector; import org.elasticsearch.search.aggregations.LeafBucketCollectorBase; import org.elasticsearch.search.aggregations.bucket.BucketsAggregator; +import org.elasticsearch.search.aggregations.bucket.terms.BucketAndOrd; import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.xcontent.ToXContentFragment; @@ -27,6 +30,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; @@ -54,7 +58,6 @@ public PathHierarchyAggregator( bucketOrds = new BytesRefHash(1, context.bigArrays()); order.validate(this); this.order = order; - this.partiallyBuiltBucketComparator = order == null ? null : order.partiallyBuiltBucketComparator(b -> b.bucketOrd, this); this.bucketCountThresholds = bucketCountThresholds; this.minDepth = minDepth; } @@ -145,7 +148,6 @@ public boolean equals(Object obj) { private final BucketOrder order; private final long minDocCount; private final int minDepth; - protected final Comparator partiallyBuiltBucketComparator; private final BucketCountThresholds bucketCountThresholds; private final BytesRef separator; @@ -156,11 +158,11 @@ public boolean equals(Object obj) { * The LeafBucketCollector is a "Per-leaf bucket collector". It collects docs for the account of buckets. */ @Override - public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) throws IOException { + public LeafBucketCollector getLeafCollector(AggregationExecutionContext aggCtx, final LeafBucketCollector sub) throws IOException { if (valuesSource == null) { return LeafBucketCollector.NO_OP_COLLECTOR; } - final SortedBinaryDocValues values = valuesSource.bytesValues(ctx); + final SortedBinaryDocValues values = valuesSource.bytesValues(aggCtx.getLeafReaderContext()); return new LeafBucketCollectorBase(sub, values) { final BytesRefBuilder previous = new BytesRefBuilder(); @@ -196,76 +198,76 @@ public void collect(int doc, long owningBucketOrdinal) throws IOException { } @Override - public InternalAggregation[] buildAggregations(long[] owningBucketOrdinals) throws IOException { - - InternalPathHierarchy.InternalBucket[][] topBucketsPerOrd = new InternalPathHierarchy.InternalBucket[owningBucketOrdinals.length][]; - InternalPathHierarchy[] results = new InternalPathHierarchy[owningBucketOrdinals.length]; - - for (int ordIdx = 0; ordIdx < owningBucketOrdinals.length; ordIdx++) { - assert owningBucketOrdinals[ordIdx] == 0; - - final int size = (int) Math.min(bucketOrds.size(), bucketCountThresholds.getShardSize()); - PathSortedTree pathSortedTree = new PathSortedTree<>( - partiallyBuiltBucketComparator, - size - ); + public InternalAggregation[] buildAggregations(LongArray owningBucketOrdinals) throws IOException { + try ( + ObjectArray topBucketsPerOrd = bigArrays().newObjectArray(owningBucketOrdinals.size()) + ) { + InternalPathHierarchy[] results = new InternalPathHierarchy[Math.toIntExact(owningBucketOrdinals.size())]; + + for (long ordIdx = 0; ordIdx < owningBucketOrdinals.size(); ordIdx++) { + assert owningBucketOrdinals.get(ordIdx) == 0; + + final int size = (int) Math.min(bucketOrds.size(), bucketCountThresholds.getShardSize()); + + PathSortedTree pathSortedTree = new PathSortedTree<>( + order.comparator(), + size + ); + + InternalPathHierarchy.InternalBucket spare; + for (int i = 0; i < bucketOrds.size(); i++) { + spare = new InternalPathHierarchy.InternalBucket(0, null, null, new BytesRef(), 0, 0, null); + BytesRef term = new BytesRef(); + bucketOrds.get(i, term); + String quotedPattern = Pattern.quote(separator.utf8ToString()); + String[] paths = term.utf8ToString().split(quotedPattern, -1); + + String[] pathsForTree; + if (minDepth > 0) { + pathsForTree = Arrays.copyOfRange(paths, minDepth, paths.length); + } else { + pathsForTree = paths; + } - InternalPathHierarchy.InternalBucket spare; - for (int i = 0; i < bucketOrds.size(); i++) { - spare = new InternalPathHierarchy.InternalBucket(0, null, null, new BytesRef(), 0, 0, null); - BytesRef term = new BytesRef(); - bucketOrds.get(i, term); + spare.termBytes = BytesRef.deepCopyOf(term); + spare.level = pathsForTree.length - 1; + spare.docCount = bucketDocCount(i); + spare.basename = paths[paths.length - 1]; + spare.minDepth = minDepth; + spare.bucketOrd = i; + spare.paths = paths; - String quotedPattern = Pattern.quote(separator.utf8ToString()); + pathSortedTree.add(pathsForTree, spare); + } - String[] paths = term.utf8ToString().split(quotedPattern, -1); + topBucketsPerOrd.set(ordIdx, new InternalPathHierarchy.InternalBucket[size]); - String[] pathsForTree; + long otherHierarchyNodes = pathSortedTree.getFullSize(); - if (minDepth > 0) { - pathsForTree = Arrays.copyOfRange(paths, minDepth, paths.length); - } else { - pathsForTree = paths; + Iterator iterator = pathSortedTree.consumer(); + for (int i = 0; i < size && iterator.hasNext(); i++) { + final InternalPathHierarchy.InternalBucket bucket = iterator.next(); + topBucketsPerOrd.get(ordIdx)[i] = bucket; + otherHierarchyNodes -= 1; } - spare.termBytes = BytesRef.deepCopyOf(term); - spare.level = pathsForTree.length - 1; - spare.docCount = bucketDocCount(i); - spare.basename = paths[paths.length - 1]; - spare.minDepth = minDepth; - spare.bucketOrd = i; - spare.paths = paths; - - pathSortedTree.add(pathsForTree, spare); - + results[Math.toIntExact(ordIdx)] = new InternalPathHierarchy( + name, + Arrays.asList(topBucketsPerOrd.get(ordIdx)), + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + otherHierarchyNodes, + separator, + metadata() + ); } - // Get the top buckets - topBucketsPerOrd[ordIdx] = new InternalPathHierarchy.InternalBucket[size]; - long otherHierarchyNodes = pathSortedTree.getFullSize(); - Iterator iterator = pathSortedTree.consumer(); - for (int i = 0; i < size; i++) { - final InternalPathHierarchy.InternalBucket bucket = iterator.next(); - topBucketsPerOrd[ordIdx][i] = bucket; - otherHierarchyNodes -= 1; - } - - results[ordIdx] = new InternalPathHierarchy( - name, - Arrays.asList(topBucketsPerOrd[ordIdx]), - order, - minDocCount, - bucketCountThresholds.getRequiredSize(), - bucketCountThresholds.getShardSize(), - otherHierarchyNodes, - separator, - metadata() - ); - } - // Build sub-aggregations for pruned buckets - buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); + buildSubAggsForAllBuckets(topBucketsPerOrd, b -> b.bucketOrd, (b, aggregations) -> b.aggregations = aggregations); - return results; + return results; + } } @Override @@ -283,6 +285,28 @@ public InternalAggregation buildEmptyAggregation() { ); } + InternalPathHierarchy buildAggregation( + String name, + List buckets, + BucketOrder order, + long minDocCount, + long otherHierarchyNodes, + BytesRef separator, + Map metadata + ) { + return new InternalPathHierarchy( + name, + buckets, + order, + minDocCount, + bucketCountThresholds.getRequiredSize(), + bucketCountThresholds.getShardSize(), + otherHierarchyNodes, + separator, + metadata + ); + } + @Override protected void doClose() { Releasables.close(bucketOrds); diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java index 103c461..f67fb72 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/PathHierarchyAggregatorFactory.java @@ -4,7 +4,6 @@ import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; -import org.apache.lucene.util.FutureArrays; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortingBinaryDocValues; import org.elasticsearch.search.aggregations.Aggregator; @@ -25,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Map; /** @@ -191,7 +191,7 @@ public boolean advanceExact(int docId) throws IOException { for (int offset = 0; offset < val.length; offset++) { // it is a separator if (val.length - offset >= separator.length - && FutureArrays.equals( + && Arrays.equals( separator.bytes, separator.offset, separator.offset + separator.length, From 613bec6063a1116b89fa860aeb1e4221e795ef50 Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Thu, 3 Jul 2025 15:27:08 +0200 Subject: [PATCH 04/15] update the LICENSE to AGPL --- LICENSE | 682 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 2 +- 2 files changed, 662 insertions(+), 22 deletions(-) diff --git a/LICENSE b/LICENSE index e92b4d2..be3f7b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,661 @@ -The MIT License (MIT) - -Copyright (c) 2018 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 0f2c7a4..8647e63 100644 --- a/README.md +++ b/README.md @@ -327,4 +327,4 @@ GET /calendar/_search?size=0 License ------- -This software is under The MIT License (MIT). +This software is under AGPL (GNU Affero General Public License) From 828d165c9a3419e42234d8249e81c060a980eac4 Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Mon, 7 Jul 2025 11:47:20 +0200 Subject: [PATCH 05/15] add the yaml REST test framework dependency to the build.gradle file --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 749017e..5cd6931 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ esplugin { dependencies { implementation "org.elasticsearch:elasticsearch:${es_version}" yamlRestTestImplementation "org.elasticsearch.test:framework:${es_version}" + yamlRestTestImplementation "org.elasticsearch.test:yaml-rest-runner:${es_version}" yamlRestTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" } From e74d82ffcd82a8793bdc40dbfd700f3df9024004 Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Mon, 7 Jul 2025 12:31:07 +0200 Subject: [PATCH 06/15] add two config files for the spotless plugin and import order --- config/elastic.importorder | 7 + config/formatterConfig.xml | 390 +++++++++++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 config/elastic.importorder create mode 100644 config/formatterConfig.xml diff --git a/config/elastic.importorder b/config/elastic.importorder new file mode 100644 index 0000000..78e051e --- /dev/null +++ b/config/elastic.importorder @@ -0,0 +1,7 @@ +#Eclipse configuration for import order for Elasticsearch +0= +1=com +2=org +3=java +4=javax +5=\# diff --git a/config/formatterConfig.xml b/config/formatterConfig.xml new file mode 100644 index 0000000..6291531 --- /dev/null +++ b/config/formatterConfig.xml @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 08abfeddd86dbe6ff1a00ed965a96012c900ee2e Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Fri, 1 Aug 2025 16:10:22 +0200 Subject: [PATCH 07/15] add a new Github Action workflow when releasing the plugin --- .github/workflows/{build.yml => build.yaml} | 4 +- .github/workflows/release.yaml | 59 +++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) rename .github/workflows/{build.yml => build.yaml} (88%) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yaml similarity index 88% rename from .github/workflows/build.yml rename to .github/workflows/build.yaml index 12f5395..0bba6f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -name: Build +name: compile-and-test on: pull_request: @@ -13,7 +13,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Build with Gradle diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..5ad6f80 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,59 @@ +name: publish + +on: + push: + tags: ['v*'] + release: + types: [published] + +jobs: + build-artifact: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Tag Version Name Extraction + id: version + shell: bash + run: | + if [[ -n "${{ github.event.release.tag_name }}" ]]; then + # Relase Github + TAG_NAME="${{ github.event.release.tag_name }}" + echo "📋 Triggered by GitHub release: $TAG_NAME" + else + # Just a new tag + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "🏷️ Triggered by Git tag: $TAG_NAME" + fi + + # Get rid of the 'v', e.g. v8.18.1.0 -> 8.18.1.0 + VERSION=${TAG_NAME#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Java Compilation + run: ./gradlew -Pplugin_version=${{ steps.version.outputs.version }} clean assemble --no-daemon + + - name: Upload Plugin Artifact + uses: actions/upload-artifact@v4 + with: + name: pathhierarchy-aggregation-${{ steps.version.outputs.version }} + path: build/distributions/*.zip + + - name: Attach ZIP to GitHub Release + uses: softprops/action-gh-release@v2 + if: github.event.release.tag_name != '' + with: + files: build/distributions/pathhierarchy-aggregation-${{ steps.version.outputs.version }}.zip + tag_name: ${{ github.event.release.tag_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6eece8bcc015a41fd85adc9088bf9dbeeb320fcc Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Mon, 4 Aug 2025 15:36:11 +0200 Subject: [PATCH 08/15] elasticsearch version 8.19 --- gradle.properties | 4 ++-- .../aggregations/bucket/DateHierarchyAggregationBuilder.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5042b70..a46e74b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -es_version = 8.18.1 -plugin_version = 8.18.1.0 +es_version = 8.19.0 +plugin_version = 8.19.0.0 diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java index 402337d..ded77c4 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregationBuilder.java @@ -63,7 +63,7 @@ public TransportVersion getMinimalSupportedVersion() { dateFieldUnits.put("months", new IntervalConfig(Rounding.DateTimeUnit.MONTH_OF_YEAR, "MM")); dateFieldUnits.put("days", new IntervalConfig(Rounding.DateTimeUnit.DAY_OF_MONTH, "dd")); dateFieldUnits.put("hours", new IntervalConfig(Rounding.DateTimeUnit.HOUR_OF_DAY, "hh")); - dateFieldUnits.put("minutes", new IntervalConfig(Rounding.DateTimeUnit.MINUTES_OF_HOUR, "mm")); + dateFieldUnits.put("minutes", new IntervalConfig(Rounding.DateTimeUnit.MINUTE_OF_HOUR, "mm")); dateFieldUnits.put("seconds", new IntervalConfig(Rounding.DateTimeUnit.SECOND_OF_MINUTE, "ss")); INTERVAL_CONFIG = unmodifiableMap(dateFieldUnits); } From d51f4505550fb9bc0b42de70a61b71f943ad51e0 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Wed, 13 Aug 2025 13:59:55 +0200 Subject: [PATCH 09/15] Add cluster info test --- .../resources/rest-api-spec/test/PathHierarchy/10_basic.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yamlRestTest/resources/rest-api-spec/test/PathHierarchy/10_basic.yml b/src/yamlRestTest/resources/rest-api-spec/test/PathHierarchy/10_basic.yml index e190d87..4acd545 100644 --- a/src/yamlRestTest/resources/rest-api-spec/test/PathHierarchy/10_basic.yml +++ b/src/yamlRestTest/resources/rest-api-spec/test/PathHierarchy/10_basic.yml @@ -8,3 +8,8 @@ nodes.info: {} - match: {nodes.$master.plugins.0.name: pathhierarchy-aggregation} + + - do: + info: {} + + - match: { cluster_name: "yamlRestTest" } From 34892458b173914694e8e51f184eceeb14f30602 Mon Sep 17 00:00:00 2001 From: Damien Garaud Date: Wed, 13 Aug 2025 17:22:15 +0200 Subject: [PATCH 10/15] fix the build.gradle for testClusters avoid having a weird 405 HTTP on 'GET /' when you './gradlew run'. --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 5cd6931..c2e6b65 100644 --- a/build.gradle +++ b/build.gradle @@ -74,3 +74,10 @@ tasks.named("yamlRestTest").configure { tasks.named("test").configure { systemProperty 'tests.security.manager', 'false' } + +// Make sure the ES distribution used for rest tests is the "complete" variant +testClusters.configureEach { + testDistribution = 'DEFAULT' + // disable security to disable failing warnings + setting 'xpack.security.enabled', 'false' +} From 958367d6ad0d65e68ca2c36d912662efe3a84359 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Fri, 12 Sep 2025 15:14:23 +0200 Subject: [PATCH 11/15] Add local dev .sdkmanrc --- .sdkmanrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..da9308a --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1 @@ +java=17.0.15-tem From 1b9a51edd68416ac76fbb54482dd0ea2ec73bbe4 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Fri, 12 Sep 2025 15:14:41 +0200 Subject: [PATCH 12/15] Bump ES to 8.19.3 and gradle to 8.14 --- gradle.properties | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 ++++----- gradlew.bat | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/gradle.properties b/gradle.properties index a46e74b..f7141bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -es_version = 8.19.0 -plugin_version = 8.19.0.0 +es_version = 8.19.3 +plugin_version = 8.19.3.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 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

Gn4F+B~IivciU9^t0j-Mgao3 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 DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From fd15bb803db2d9760a77b9d79c275eb3d481fca7 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Wed, 8 Oct 2025 16:32:35 +0200 Subject: [PATCH 13/15] Remove .sdkmanrc --- .sdkmanrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc deleted file mode 100644 index da9308a..0000000 --- a/.sdkmanrc +++ /dev/null @@ -1 +0,0 @@ -java=17.0.15-tem From f6105ffc2cedb8d187b4a66a6d7f7a06cda1e0a6 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Wed, 8 Oct 2025 17:12:07 +0200 Subject: [PATCH 14/15] Bump to 8.19.4.0 --- build.gradle | 64 +++++++++++++++++++++++------------------------ gradle.properties | 4 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index c2e6b65..4e4bb13 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,9 @@ +/* +Gradle build file for development building, testing, and packaging. +*/ + buildscript { repositories { - mavenLocal() mavenCentral() } @@ -19,38 +22,14 @@ repositories { mavenCentral() } -// add -Xlint:deprecation to check the Elasticsearch deprecation warning at compile-time -allprojects { - gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:deprecation" - options.compilerArgs << "-Xlint:unchecked" - } - } -} - group = 'org.elasticsearch.plugin' version = "${plugin_version}" -def versions = org.elasticsearch.gradle.VersionProperties.versions - apply plugin: 'java' apply plugin: 'idea' apply plugin: 'elasticsearch.esplugin' apply plugin: 'elasticsearch.yaml-rest-test' -// automatic formatting configuration (the same configuration as elasticsearch) -spotless { - java { - importOrderFile('config/elastic.importorder') // import order file as exported from elastic - eclipse().configFile('config/formatterConfig.xml') - trimTrailingWhitespace() - target 'src/**/*.java' - } -} - -check.dependsOn spotlessCheck - esplugin { name 'pathhierarchy-aggregation' description 'Return a path hierarchy aggregation' @@ -59,7 +38,7 @@ esplugin { noticeFile = rootProject.file('README.md') } - +def versions = org.elasticsearch.gradle.VersionProperties.versions dependencies { implementation "org.elasticsearch:elasticsearch:${es_version}" yamlRestTestImplementation "org.elasticsearch.test:framework:${es_version}" @@ -67,17 +46,38 @@ dependencies { yamlRestTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" } -tasks.named("yamlRestTest").configure { - systemProperty 'tests.security.manager', 'false' +// add -Xlint:deprecation to check the Elasticsearch deprecation warning at compile-time +allprojects { + gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:deprecation" + options.compilerArgs << "-Xlint:unchecked" + } + } } -tasks.named("test").configure { - systemProperty 'tests.security.manager', 'false' +// automatic formatting configuration (the same configuration as elasticsearch) +spotless { + java { + importOrderFile('config/elastic.importorder') // import order file as exported from elastic + eclipse().configFile('config/formatterConfig.xml') + trimTrailingWhitespace() + target 'src/**/*.java' + } } -// Make sure the ES distribution used for rest tests is the "complete" variant +check.dependsOn spotlessCheck + testClusters.configureEach { + // Make sure the ES distribution used for rest tests is the "complete" variant testDistribution = 'DEFAULT' - // disable security to disable failing warnings + + // Disable security (avoids annoying warnings/failures with xpack features) setting 'xpack.security.enabled', 'false' + + // Logging levels for debugging: logs are located in build/testclusters/runTask-0/logs/ + setting 'logger._root', 'DEBUG' + setting 'logger.org.elasticsearch.plugins', 'DEBUG' + setting 'logger.org.elasticsearch.cluster', 'DEBUG' + setting 'logger.org.elasticsearch.cluster.metadata', 'TRACE' } diff --git a/gradle.properties b/gradle.properties index f7141bb..3ed2ee9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -es_version = 8.19.3 -plugin_version = 8.19.3.0 +es_version = 8.19.4 +plugin_version = 8.19.4.0 From e5870f23957f9cb609bc6f81850f18e314de2863 Mon Sep 17 00:00:00 2001 From: Bapt Abl Date: Wed, 8 Oct 2025 17:25:21 +0200 Subject: [PATCH 15/15] Remove dev comments --- .../search/aggregations/bucket/DateHierarchyAggregator.java | 6 ------ .../search/aggregations/bucket/InternalPathHierarchy.java | 1 - 2 files changed, 7 deletions(-) diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java index adafdc1..60e77d4 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/DateHierarchyAggregator.java @@ -189,10 +189,6 @@ public void collect(int doc, long bucket) throws IOException { @Override public InternalAggregation[] buildAggregations(LongArray owningBucketOrdinals) throws IOException { - // InternalDateHierarchy.InternalBucket[][] topBucketsPerOrd = new - // InternalDateHierarchy.InternalBucket[owningBucketOrdinals.length][]; - // InternalDateHierarchy[] results = new InternalDateHierarchy[owningBucketOrdinals.length]; - try ( ObjectArray topBucketsPerOrd = bigArrays().newObjectArray(owningBucketOrdinals.size()) ) { @@ -200,7 +196,6 @@ public InternalAggregation[] buildAggregations(LongArray owningBucketOrdinals) t InternalDateHierarchy[] results = new InternalDateHierarchy[Math.toIntExact(owningBucketOrdinals.size())]; for (int ordIdx = 0; ordIdx < owningBucketOrdinals.size(); ordIdx++) { - // assert owningBucketOrdinals[ordIdx] == 0; assert owningBucketOrdinals.get(ordIdx) == 0; // build buckets and store them sorted @@ -231,7 +226,6 @@ public InternalAggregation[] buildAggregations(LongArray owningBucketOrdinals) t // Get the top buckets topBucketsPerOrd.set(ordIdx, new InternalDateHierarchy.InternalBucket[size]); - // topBucketsPerOrd[ordIdx] = new InternalDateHierarchy.InternalBucket[size]; long otherHierarchyNodes = pathSortedTree.getFullSize(); Iterator iterator = pathSortedTree.consumer(); for (int i = 0; i < size; i++) { diff --git a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java index 29884d5..8ba84ed 100644 --- a/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java +++ b/src/main/java/org/opendatasoft/elasticsearch/search/aggregations/bucket/InternalPathHierarchy.java @@ -329,7 +329,6 @@ protected InternalBucket reduceBucket(List buckets, AggregationR @Override public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { - // builder.field(SUM_OF_OTHER_HIERARCHY_NODES.getPreferredName(), otherHierarchyNodes); Iterator bucketIterator = buckets.iterator(); builder.startArray(CommonFields.BUCKETS.getPreferredName()); InternalBucket prevBucket = null;