Optimize ST_EXTENT_AGG for geo_shape and cartesian_shape (#119889)

Support for `ST_EXTENT_AGG` was added in https://github.com/elastic/elasticsearch/pull/118829, and then partially optimized in https://github.com/elastic/elasticsearch/pull/118829. This optimization worked only for cartesian_shape fields, and worked by extracting the Extent from the doc-values and re-encoding it as a WKB `BBOX` geometry. This does not work for geo_shape, where we need to retain all 6 integers stored in the doc-values, in order to perform the datelline choice only at reduce time during the final phase of the aggregation.

Since both geo_shape and cartesian_shape perform the aggregations using integers, and the original Extent values in the doc-values are integers, this PR expands the previous optimization by:
* Saving all Extent values into a multi-valued field in an IntBlock for both cartesian_shape and geo_shape
* Simplifying the logic around merging intermediate states for all cases (geo/cartesian and grouped and non-grouped aggs)
* Widening test cases for testing more combinations of aggregations and types, and fixing a few bugs found
* Enhancing cartesian extent to convert from 6 ints to 4 ints at block loading time (for efficiency)
* Fixing bugs in both cartesian and geo extents for generating intermediate state with missing groups (flaky tests in serverless)
* Moved the int order to always match Rectangle for 4-int and Extent for 6-int cases (improved internal consistency)

Since the PR already changed the meaning of the invalid/infinite values of the intermediate state integers, it was already not compatible with the previous cluster versions. We disabled mixed-cluster testing to prevent errors as a result of that. This leaves us the opportunity to make further changes that are mixed-cluster incompatible, hence the decision to perform this consistency update now.
This commit is contained in:
Craig Taverner 2025-01-16 19:43:51 +01:00 committed by GitHub
parent a1e6f5f841
commit 40c34cd896
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 2669 additions and 965 deletions

View file

@ -0,0 +1,5 @@
pr: 119889
summary: Optimize ST_EXTENT_AGG for `geo_shape` and `cartesian_shape`
area: "ES|QL"
type: enhancement
issues: []

View file

@ -116,6 +116,9 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
boolean isValid(); boolean isValid();
Rectangle getResult(); Rectangle getResult();
/** To allow for memory optimizations through object reuse, the visitor can be reset to its initial state. */
void reset();
} }
/** /**
@ -124,18 +127,14 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
*/ */
public static class CartesianPointVisitor implements PointVisitor { public static class CartesianPointVisitor implements PointVisitor {
private double minX = Double.POSITIVE_INFINITY; private double minX = Double.POSITIVE_INFINITY;
private double minY = Double.POSITIVE_INFINITY;
private double maxX = Double.NEGATIVE_INFINITY; private double maxX = Double.NEGATIVE_INFINITY;
private double maxY = Double.NEGATIVE_INFINITY; private double maxY = Double.NEGATIVE_INFINITY;
private double minY = Double.POSITIVE_INFINITY;
public double getMinX() { public double getMinX() {
return minX; return minX;
} }
public double getMinY() {
return minY;
}
public double getMaxX() { public double getMaxX() {
return maxX; return maxX;
} }
@ -144,12 +143,16 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
return maxY; return maxY;
} }
public double getMinY() {
return minY;
}
@Override @Override
public void visitPoint(double x, double y) { public void visitPoint(double x, double y) {
minX = Math.min(minX, x); minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x); maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y); maxY = Math.max(maxY, y);
minY = Math.min(minY, y);
} }
@Override @Override
@ -160,9 +163,9 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
); );
} }
this.minX = Math.min(this.minX, minX); this.minX = Math.min(this.minX, minX);
this.minY = Math.min(this.minY, minY);
this.maxX = Math.max(this.maxX, maxX); this.maxX = Math.max(this.maxX, maxX);
this.maxY = Math.max(this.maxY, maxY); this.maxY = Math.max(this.maxY, maxY);
this.minY = Math.min(this.minY, minY);
} }
@Override @Override
@ -174,6 +177,14 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
public Rectangle getResult() { public Rectangle getResult() {
return new Rectangle(minX, maxX, maxY, minY); return new Rectangle(minX, maxX, maxY, minY);
} }
@Override
public void reset() {
minX = Double.POSITIVE_INFINITY;
maxX = Double.NEGATIVE_INFINITY;
maxY = Double.NEGATIVE_INFINITY;
minY = Double.POSITIVE_INFINITY;
}
} }
/** /**
@ -186,12 +197,12 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
* </ul> * </ul>
*/ */
public static class GeoPointVisitor implements PointVisitor { public static class GeoPointVisitor implements PointVisitor {
protected double minY = Double.POSITIVE_INFINITY; protected double top = Double.NEGATIVE_INFINITY;
protected double maxY = Double.NEGATIVE_INFINITY; protected double bottom = Double.POSITIVE_INFINITY;
protected double minNegX = Double.POSITIVE_INFINITY; protected double negLeft = Double.POSITIVE_INFINITY;
protected double maxNegX = Double.NEGATIVE_INFINITY; protected double negRight = Double.NEGATIVE_INFINITY;
protected double minPosX = Double.POSITIVE_INFINITY; protected double posLeft = Double.POSITIVE_INFINITY;
protected double maxPosX = Double.NEGATIVE_INFINITY; protected double posRight = Double.NEGATIVE_INFINITY;
private final WrapLongitude wrapLongitude; private final WrapLongitude wrapLongitude;
@ -199,69 +210,104 @@ public class SpatialEnvelopeVisitor implements GeometryVisitor<Boolean, RuntimeE
this.wrapLongitude = wrapLongitude; this.wrapLongitude = wrapLongitude;
} }
public double getTop() {
return top;
}
public double getBottom() {
return bottom;
}
public double getNegLeft() {
return negLeft;
}
public double getNegRight() {
return negRight;
}
public double getPosLeft() {
return posLeft;
}
public double getPosRight() {
return posRight;
}
@Override @Override
public void visitPoint(double x, double y) { public void visitPoint(double x, double y) {
minY = Math.min(minY, y); bottom = Math.min(bottom, y);
maxY = Math.max(maxY, y); top = Math.max(top, y);
visitLongitude(x); visitLongitude(x);
} }
@Override @Override
public void visitRectangle(double minX, double maxX, double maxY, double minY) { public void visitRectangle(double minX, double maxX, double maxY, double minY) {
this.minY = Math.min(this.minY, minY); // TODO: Fix bug with rectangle crossing the dateline (see Extent.addRectangle for correct behaviour)
this.maxY = Math.max(this.maxY, maxY); this.bottom = Math.min(this.bottom, minY);
this.top = Math.max(this.top, maxY);
visitLongitude(minX); visitLongitude(minX);
visitLongitude(maxX); visitLongitude(maxX);
} }
private void visitLongitude(double x) { private void visitLongitude(double x) {
if (x >= 0) { if (x >= 0) {
minPosX = Math.min(minPosX, x); posLeft = Math.min(posLeft, x);
maxPosX = Math.max(maxPosX, x); posRight = Math.max(posRight, x);
} else { } else {
minNegX = Math.min(minNegX, x); negLeft = Math.min(negLeft, x);
maxNegX = Math.max(maxNegX, x); negRight = Math.max(negRight, x);
} }
} }
@Override @Override
public boolean isValid() { public boolean isValid() {
return minY != Double.POSITIVE_INFINITY; return bottom != Double.POSITIVE_INFINITY;
} }
@Override @Override
public Rectangle getResult() { public Rectangle getResult() {
return getResult(minNegX, minPosX, maxNegX, maxPosX, maxY, minY, wrapLongitude); return getResult(top, bottom, negLeft, negRight, posLeft, posRight, wrapLongitude);
} }
protected static Rectangle getResult( @Override
double minNegX, public void reset() {
double minPosX, bottom = Double.POSITIVE_INFINITY;
double maxNegX, top = Double.NEGATIVE_INFINITY;
double maxPosX, negLeft = Double.POSITIVE_INFINITY;
double maxY, negRight = Double.NEGATIVE_INFINITY;
double minY, posLeft = Double.POSITIVE_INFINITY;
posRight = Double.NEGATIVE_INFINITY;
}
public static Rectangle getResult(
double top,
double bottom,
double negLeft,
double negRight,
double posLeft,
double posRight,
WrapLongitude wrapLongitude WrapLongitude wrapLongitude
) { ) {
assert Double.isFinite(maxY); assert Double.isFinite(top);
if (Double.isInfinite(minPosX)) { if (posRight == Double.NEGATIVE_INFINITY) {
return new Rectangle(minNegX, maxNegX, maxY, minY); return new Rectangle(negLeft, negRight, top, bottom);
} else if (Double.isInfinite(minNegX)) { } else if (negLeft == Double.POSITIVE_INFINITY) {
return new Rectangle(minPosX, maxPosX, maxY, minY); return new Rectangle(posLeft, posRight, top, bottom);
} else { } else {
return switch (wrapLongitude) { return switch (wrapLongitude) {
case NO_WRAP -> new Rectangle(minNegX, maxPosX, maxY, minY); case NO_WRAP -> new Rectangle(negLeft, posRight, top, bottom);
case WRAP -> maybeWrap(minNegX, minPosX, maxNegX, maxPosX, maxY, minY); case WRAP -> maybeWrap(top, bottom, negLeft, negRight, posLeft, posRight);
}; };
} }
} }
private static Rectangle maybeWrap(double minNegX, double minPosX, double maxNegX, double maxPosX, double maxY, double minY) { private static Rectangle maybeWrap(double top, double bottom, double negLeft, double negRight, double posLeft, double posRight) {
double unwrappedWidth = maxPosX - minNegX; double unwrappedWidth = posRight - negLeft;
double wrappedWidth = 360 + maxNegX - minPosX; double wrappedWidth = 360 + negRight - posLeft;
return unwrappedWidth <= wrappedWidth return unwrappedWidth <= wrappedWidth
? new Rectangle(minNegX, maxPosX, maxY, minY) ? new Rectangle(negLeft, posRight, top, bottom)
: new Rectangle(minPosX, maxNegX, maxY, minY); : new Rectangle(posLeft, negRight, top, bottom);
} }
} }

View file

@ -33,6 +33,7 @@ import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.IndexVersions; import org.elasticsearch.index.IndexVersions;
import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.BlockLoader;
import org.elasticsearch.index.mapper.DocumentParserContext; import org.elasticsearch.index.mapper.DocumentParserContext;
import org.elasticsearch.index.mapper.DocumentParsingException; import org.elasticsearch.index.mapper.DocumentParsingException;
import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMapper;
@ -46,7 +47,6 @@ import org.elasticsearch.legacygeo.XShapeCollection;
import org.elasticsearch.legacygeo.builders.ShapeBuilder; import org.elasticsearch.legacygeo.builders.ShapeBuilder;
import org.elasticsearch.legacygeo.parsers.ShapeParser; import org.elasticsearch.legacygeo.parsers.ShapeParser;
import org.elasticsearch.legacygeo.query.LegacyGeoShapeQueryProcessor; import org.elasticsearch.legacygeo.query.LegacyGeoShapeQueryProcessor;
import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParser;
import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Point;
@ -84,6 +84,7 @@ import java.util.stream.Collectors;
* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0)) * "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))
* *
* @deprecated use the field mapper in the spatial module * @deprecated use the field mapper in the spatial module
* TODO: Remove this class once we no longer need to supported reading 7.x indices that might have this field type
*/ */
@Deprecated @Deprecated
public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<ShapeBuilder<?, ?, ?>> { public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<ShapeBuilder<?, ?, ?>> {
@ -533,14 +534,9 @@ public class LegacyGeoShapeFieldMapper extends AbstractShapeGeometryFieldMapper<
} }
@Override @Override
protected boolean isBoundsExtractionSupported() { public BlockLoader blockLoader(BlockLoaderContext blContext) {
// Extracting bounds for geo shapes is not implemented yet. // Legacy geo-shapes do not support doc-values, we can only lead from source in ES|QL
return false; return blockLoaderFromSource(blContext);
}
@Override
protected CoordinateEncoder coordinateEncoder() {
return CoordinateEncoder.GEO;
} }
} }

View file

@ -180,12 +180,6 @@ public abstract class AbstractGeometryFieldMapper<T> extends FieldMapper {
}; };
} }
@Override
public BlockLoader blockLoader(BlockLoaderContext blContext) {
// Currently we can only load from source in ESQL
return blockLoaderFromSource(blContext);
}
protected BlockLoader blockLoaderFromSource(BlockLoaderContext blContext) { protected BlockLoader blockLoaderFromSource(BlockLoaderContext blContext) {
ValueFetcher fetcher = valueFetcher(blContext.sourcePaths(name()), nullValue, GeometryFormatterFactory.WKB); ValueFetcher fetcher = valueFetcher(blContext.sourcePaths(name()), nullValue, GeometryFormatterFactory.WKB);
// TODO consider optimization using BlockSourceReader.lookupFromFieldNames(blContext.fieldNames(), name()) // TODO consider optimization using BlockSourceReader.lookupFromFieldNames(blContext.fieldNames(), name())

View file

@ -10,16 +10,12 @@ package org.elasticsearch.index.mapper;
import org.apache.lucene.index.BinaryDocValues; import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.lucene.spatial.Extent;
import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.lucene.spatial.GeometryDocValueReader; import org.elasticsearch.lucene.spatial.GeometryDocValueReader;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -75,29 +71,27 @@ public abstract class AbstractShapeGeometryFieldMapper<T> extends AbstractGeomet
@Override @Override
protected Object nullValueAsSource(T nullValue) { protected Object nullValueAsSource(T nullValue) {
// we don't support null value fors shapes // we don't support null value for shapes
return nullValue; return nullValue;
} }
@Override protected static class BoundsBlockLoader extends BlockDocValuesReader.DocValuesBlockLoader {
public BlockLoader blockLoader(BlockLoaderContext blContext) { private final String fieldName;
return blContext.fieldExtractPreference() == FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS && isBoundsExtractionSupported()
? new BoundsBlockLoader(name(), coordinateEncoder()) protected BoundsBlockLoader(String fieldName) {
: blockLoaderFromSource(blContext); this.fieldName = fieldName;
} }
protected abstract boolean isBoundsExtractionSupported(); protected void writeExtent(BlockLoader.IntBuilder builder, Extent extent) {
// We store the 6 values as a single multi-valued field, in the same order as the fields in the Extent class
protected abstract CoordinateEncoder coordinateEncoder(); builder.beginPositionEntry();
builder.appendInt(extent.top);
// Visible for testing builder.appendInt(extent.bottom);
static class BoundsBlockLoader extends BlockDocValuesReader.DocValuesBlockLoader { builder.appendInt(extent.negLeft);
private final String fieldName; builder.appendInt(extent.negRight);
private final CoordinateEncoder encoder; builder.appendInt(extent.posLeft);
builder.appendInt(extent.posRight);
BoundsBlockLoader(String fieldName, CoordinateEncoder encoder) { builder.endPositionEntry();
this.fieldName = fieldName;
this.encoder = encoder;
} }
@Override @Override
@ -107,7 +101,7 @@ public abstract class AbstractShapeGeometryFieldMapper<T> extends AbstractGeomet
public BlockLoader.Block read(BlockLoader.BlockFactory factory, BlockLoader.Docs docs) throws IOException { public BlockLoader.Block read(BlockLoader.BlockFactory factory, BlockLoader.Docs docs) throws IOException {
var binaryDocValues = context.reader().getBinaryDocValues(fieldName); var binaryDocValues = context.reader().getBinaryDocValues(fieldName);
var reader = new GeometryDocValueReader(); var reader = new GeometryDocValueReader();
try (var builder = factory.bytesRefs(docs.count())) { try (var builder = factory.ints(docs.count())) {
for (int i = 0; i < docs.count(); i++) { for (int i = 0; i < docs.count(); i++) {
read(binaryDocValues, docs.get(i), reader, builder); read(binaryDocValues, docs.get(i), reader, builder);
} }
@ -119,27 +113,17 @@ public abstract class AbstractShapeGeometryFieldMapper<T> extends AbstractGeomet
public void read(int docId, BlockLoader.StoredFields storedFields, BlockLoader.Builder builder) throws IOException { public void read(int docId, BlockLoader.StoredFields storedFields, BlockLoader.Builder builder) throws IOException {
var binaryDocValues = context.reader().getBinaryDocValues(fieldName); var binaryDocValues = context.reader().getBinaryDocValues(fieldName);
var reader = new GeometryDocValueReader(); var reader = new GeometryDocValueReader();
read(binaryDocValues, docId, reader, (BytesRefBuilder) builder); read(binaryDocValues, docId, reader, (IntBuilder) builder);
} }
private void read(BinaryDocValues binaryDocValues, int doc, GeometryDocValueReader reader, BytesRefBuilder builder) private void read(BinaryDocValues binaryDocValues, int doc, GeometryDocValueReader reader, IntBuilder builder)
throws IOException { throws IOException {
if (binaryDocValues.advanceExact(doc) == false) { if (binaryDocValues.advanceExact(doc) == false) {
builder.appendNull(); builder.appendNull();
return; return;
} }
reader.reset(binaryDocValues.binaryValue()); reader.reset(binaryDocValues.binaryValue());
var extent = reader.getExtent(); writeExtent(builder, reader.getExtent());
// This is rather silly: an extent is already encoded as ints, but we convert it to Rectangle to
// preserve its properties as a WKB shape, only to convert it back to ints when we compute the
// aggregation. An obvious optimization would be to avoid this back-and-forth conversion.
var rectangle = new Rectangle(
encoder.decodeX(extent.minX()),
encoder.decodeX(extent.maxX()),
encoder.decodeY(extent.maxY()),
encoder.decodeY(extent.minY())
);
builder.appendBytesRef(new BytesRef(WellKnownBinary.toWKB(rectangle, ByteOrder.LITTLE_ENDIAN)));
} }
@Override @Override
@ -151,7 +135,7 @@ public abstract class AbstractShapeGeometryFieldMapper<T> extends AbstractGeomet
@Override @Override
public BlockLoader.Builder builder(BlockLoader.BlockFactory factory, int expectedCount) { public BlockLoader.Builder builder(BlockLoader.BlockFactory factory, int expectedCount) {
return factory.bytesRefs(expectedCount); return factory.ints(expectedCount);
} }
} }
} }

View file

@ -1,114 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.index.mapper;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.core.Strings;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geo.ShapeTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.lucene.spatial.BinaryShapeDocValuesField;
import org.elasticsearch.lucene.spatial.CartesianShapeIndexer;
import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.hamcrest.RectangleMatcher;
import org.elasticsearch.test.hamcrest.WellKnownBinaryBytesRefMatcher;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.IntStream;
public class AbstractShapeGeometryFieldMapperTests extends ESTestCase {
public void testCartesianBoundsBlockLoader() throws IOException {
testBoundsBlockLoaderAux(
CoordinateEncoder.CARTESIAN,
() -> ShapeTestUtils.randomGeometryWithoutCircle(0, false),
CartesianShapeIndexer::new,
SpatialEnvelopeVisitor::visitCartesian
);
}
// TODO when we turn this optimization on for geo, this test should pass.
public void ignoreTestGeoBoundsBlockLoader() throws IOException {
testBoundsBlockLoaderAux(
CoordinateEncoder.GEO,
() -> GeometryTestUtils.randomGeometryWithoutCircle(0, false),
field -> new GeoShapeIndexer(Orientation.RIGHT, field),
g -> SpatialEnvelopeVisitor.visitGeo(g, SpatialEnvelopeVisitor.WrapLongitude.WRAP)
);
}
private static void testBoundsBlockLoaderAux(
CoordinateEncoder encoder,
Supplier<Geometry> generator,
Function<String, ShapeIndexer> indexerFactory,
Function<Geometry, Optional<Rectangle>> visitor
) throws IOException {
var geometries = IntStream.range(0, 50).mapToObj(i -> generator.get()).toList();
var loader = new AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType.BoundsBlockLoader("field", encoder);
try (Directory directory = newDirectory()) {
try (var iw = new RandomIndexWriter(random(), directory)) {
for (Geometry geometry : geometries) {
var shape = new BinaryShapeDocValuesField("field", encoder);
shape.add(indexerFactory.apply("field").indexShape(geometry), geometry);
var doc = new Document();
doc.add(shape);
iw.addDocument(doc);
}
}
var expected = new ArrayList<Rectangle>();
var byteRefResults = new ArrayList<BytesRef>();
int currentIndex = 0;
try (DirectoryReader reader = DirectoryReader.open(directory)) {
for (var leaf : reader.leaves()) {
LeafReader leafReader = leaf.reader();
int numDocs = leafReader.numDocs();
// We specifically check just the even indices, to verify the loader can skip documents correctly.
int[] array = evenArray(numDocs);
for (int i = 0; i < array.length; i += 1) {
expected.add(visitor.apply(geometries.get(array[i] + currentIndex)).get());
}
try (var block = (TestBlock) loader.reader(leaf).read(TestBlock.factory(leafReader.numDocs()), TestBlock.docs(array))) {
for (int i = 0; i < block.size(); i++) {
byteRefResults.add((BytesRef) block.get(i));
}
}
currentIndex += numDocs;
}
}
for (int i = 0; i < expected.size(); i++) {
Rectangle rectangle = expected.get(i);
var geoString = rectangle.toString();
assertThat(
Strings.format("geometry '%s' wasn't extracted correctly", geoString),
byteRefResults.get(i),
WellKnownBinaryBytesRefMatcher.encodes(RectangleMatcher.closeToFloat(rectangle, 1e-3, encoder))
);
}
}
}
private static int[] evenArray(int maxIndex) {
return IntStream.range(0, maxIndex / 2).map(x -> x * 2).toArray();
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.index.mapper;
import org.apache.lucene.document.Document;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.index.RandomIndexWriter;
import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.core.Strings;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geo.ShapeTestUtils;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.lucene.spatial.BinaryShapeDocValuesField;
import org.elasticsearch.lucene.spatial.CartesianShapeIndexer;
import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.hamcrest.RectangleMatcher;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude;
import static org.elasticsearch.common.geo.Orientation.RIGHT;
public class ShapeGeometryFieldMapperTests extends ESTestCase {
public void testCartesianBoundsBlockLoader() throws IOException {
testBoundsBlockLoader(
CoordinateEncoder.CARTESIAN,
() -> ShapeTestUtils.randomGeometryWithoutCircle(0, false),
CartesianShapeIndexer::new,
SpatialEnvelopeVisitor::visitCartesian,
ShapeGeometryFieldMapperTests::makeCartesianRectangle
);
}
// TODO: Re-enable this test after fixing the bug in the ShapeEnvelopeVisitor regarding Rectangle crossing the dateline
// Currently it is flaky if the geometries include a Rectangle like one defined in the test below
public void ignoreTestGeoBoundsBlockLoader() throws IOException {
testBoundsBlockLoader(
CoordinateEncoder.GEO,
() -> normalize(GeometryTestUtils.randomGeometryWithoutCircle(0, false)),
field -> new GeoShapeIndexer(RIGHT, field),
g -> SpatialEnvelopeVisitor.visitGeo(g, SpatialEnvelopeVisitor.WrapLongitude.WRAP),
ShapeGeometryFieldMapperTests::makeGeoRectangle
);
}
// TODO: Re-enable this test after fixing the bug in the SpatialEnvelopeVisitor regarding Rectangle crossing the dateline
// See the difference between GeoShapeIndexer.visitRectangle() and SpatialEnvelopeVisitor.GeoPointVisitor.visitRectangle()
public void ignoreTestRectangleCrossingDateline() throws IOException {
var geometries = new ArrayList<Geometry>();
geometries.add(new Rectangle(180, 51.62247094594227, -18.5, -24.902304006345503));
testBoundsBlockLoaderAux(
CoordinateEncoder.GEO,
geometries,
field -> new GeoShapeIndexer(RIGHT, field),
g -> SpatialEnvelopeVisitor.visitGeo(g, SpatialEnvelopeVisitor.WrapLongitude.WRAP),
ShapeGeometryFieldMapperTests::makeGeoRectangle
);
}
private Geometry normalize(Geometry geometry) {
return GeometryNormalizer.needsNormalize(RIGHT, geometry) ? GeometryNormalizer.apply(RIGHT, geometry) : geometry;
}
private static void testBoundsBlockLoader(
CoordinateEncoder encoder,
Supplier<Geometry> generator,
Function<String, ShapeIndexer> indexerFactory,
Function<Geometry, Optional<Rectangle>> visitor,
BiFunction<CoordinateEncoder, Object, Rectangle> rectangleMaker
) throws IOException {
var geometries = IntStream.range(0, 50).mapToObj(i -> generator.get()).toList();
testBoundsBlockLoaderAux(encoder, geometries, indexerFactory, visitor, rectangleMaker);
}
private static void testBoundsBlockLoaderAux(
CoordinateEncoder encoder,
java.util.List<Geometry> geometries,
Function<String, ShapeIndexer> indexerFactory,
Function<Geometry, Optional<Rectangle>> visitor,
BiFunction<CoordinateEncoder, Object, Rectangle> rectangleMaker
) throws IOException {
var loader = new AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType.BoundsBlockLoader("field");
try (Directory directory = newDirectory()) {
try (var iw = new RandomIndexWriter(random(), directory)) {
for (Geometry geometry : geometries) {
var shape = new BinaryShapeDocValuesField("field", encoder);
shape.add(indexerFactory.apply("field").indexShape(geometry), geometry);
var doc = new Document();
doc.add(shape);
iw.addDocument(doc);
}
}
var expected = new ArrayList<Rectangle>();
ArrayList<Object> intArrayResults = new ArrayList<>();
int currentIndex = 0;
try (DirectoryReader reader = DirectoryReader.open(directory)) {
for (var leaf : reader.leaves()) {
LeafReader leafReader = leaf.reader();
int numDocs = leafReader.numDocs();
// We specifically check just the even indices, to verify the loader can skip documents correctly.
int[] array = evenArray(numDocs);
for (int j : array) {
expected.add(visitor.apply(geometries.get(j + currentIndex)).get());
}
try (var block = (TestBlock) loader.reader(leaf).read(TestBlock.factory(leafReader.numDocs()), TestBlock.docs(array))) {
for (int i = 0; i < block.size(); i++) {
intArrayResults.add(block.get(i));
}
}
currentIndex += numDocs;
}
}
for (int i = 0; i < expected.size(); i++) {
Rectangle rectangle = expected.get(i);
var geoString = rectangle.toString();
Rectangle result = rectangleMaker.apply(encoder, intArrayResults.get(i));
assertThat(
Strings.format("geometry[%d] '%s' wasn't extracted correctly", i, geoString),
result,
RectangleMatcher.closeToFloat(rectangle, 1e-3, encoder)
);
}
}
}
private static Rectangle makeCartesianRectangle(CoordinateEncoder encoder, Object integers) {
if (integers instanceof ArrayList<?> list) {
int[] ints = list.stream().mapToInt(x -> (int) x).toArray();
if (list.size() == 6) {
// Data in order defined by Extent class
double top = encoder.decodeY(ints[0]);
double bottom = encoder.decodeY(ints[1]);
double negLeft = encoder.decodeX(ints[2]);
double negRight = encoder.decodeX(ints[3]);
double posLeft = encoder.decodeX(ints[4]);
double posRight = encoder.decodeX(ints[5]);
return new Rectangle(Math.min(negLeft, posLeft), Math.max(negRight, posRight), top, bottom);
} else if (list.size() == 4) {
// Data in order defined by Rectangle class
return new Rectangle(
encoder.decodeX(ints[0]),
encoder.decodeX(ints[1]),
encoder.decodeY(ints[2]),
encoder.decodeY(ints[3])
);
} else {
throw new IllegalArgumentException("Expected 4 or 6 integers");
}
}
throw new IllegalArgumentException("Expected an array of integers");
}
private static Rectangle makeGeoRectangle(CoordinateEncoder encoder, Object integers) {
if (integers instanceof ArrayList<?> list) {
int[] ints = list.stream().mapToInt(x -> (int) x).toArray();
if (list.size() != 6) {
throw new IllegalArgumentException("Expected 6 integers");
}
// Data in order defined by Extent class
return asGeoRectangle(ints[0], ints[1], ints[2], ints[3], ints[4], ints[5]);
}
throw new IllegalArgumentException("Expected an array of integers");
}
private static Rectangle asGeoRectangle(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) {
return SpatialEnvelopeVisitor.GeoPointVisitor.getResult(
GeoEncodingUtils.decodeLatitude(top),
GeoEncodingUtils.decodeLatitude(bottom),
negLeft <= 0 ? decodeLongitude(negLeft) : Double.POSITIVE_INFINITY,
negRight <= 0 ? decodeLongitude(negRight) : Double.NEGATIVE_INFINITY,
posLeft >= 0 ? decodeLongitude(posLeft) : Double.POSITIVE_INFINITY,
posRight >= 0 ? decodeLongitude(posRight) : Double.NEGATIVE_INFINITY,
SpatialEnvelopeVisitor.WrapLongitude.WRAP
);
}
private static int[] evenArray(int maxIndex) {
return IntStream.range(0, maxIndex / 2).map(x -> x * 2).toArray();
}
}

View file

@ -29,6 +29,7 @@ import java.util.stream.Stream;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
@ -85,6 +86,7 @@ public class AggregatorImplementer {
private final boolean stateTypeHasSeen; private final boolean stateTypeHasSeen;
private final boolean stateTypeHasFailed; private final boolean stateTypeHasFailed;
private final boolean valuesIsBytesRef; private final boolean valuesIsBytesRef;
private final boolean valuesIsArray;
private final List<IntermediateStateDesc> intermediateState; private final List<IntermediateStateDesc> intermediateState;
private final List<Parameter> createParameters; private final List<Parameter> createParameters;
@ -126,7 +128,8 @@ public class AggregatorImplementer {
elements.getPackageOf(declarationType).toString(), elements.getPackageOf(declarationType).toString(),
(declarationType.getSimpleName() + "AggregatorFunction").replace("AggregatorAggregator", "Aggregator") (declarationType.getSimpleName() + "AggregatorFunction").replace("AggregatorAggregator", "Aggregator")
); );
this.valuesIsBytesRef = BYTES_REF.equals(TypeName.get(combine.getParameters().get(combine.getParameters().size() - 1).asType())); this.valuesIsBytesRef = BYTES_REF.equals(valueTypeName());
this.valuesIsArray = TypeKind.ARRAY.equals(valueTypeKind());
intermediateState = Arrays.stream(interStateAnno).map(IntermediateStateDesc::newIntermediateStateDesc).toList(); intermediateState = Arrays.stream(interStateAnno).map(IntermediateStateDesc::newIntermediateStateDesc).toList();
} }
@ -143,10 +146,11 @@ public class AggregatorImplementer {
if (false == initReturn.isPrimitive()) { if (false == initReturn.isPrimitive()) {
return initReturn; return initReturn;
} }
String simpleName = firstUpper(initReturn.toString());
if (warnExceptions.isEmpty()) { if (warnExceptions.isEmpty()) {
return ClassName.get("org.elasticsearch.compute.aggregation", firstUpper(initReturn.toString()) + "State"); return ClassName.get("org.elasticsearch.compute.aggregation", simpleName + "State");
} }
return ClassName.get("org.elasticsearch.compute.aggregation", firstUpper(initReturn.toString()) + "FallibleState"); return ClassName.get("org.elasticsearch.compute.aggregation", simpleName + "FallibleState");
} }
static String valueType(ExecutableElement init, ExecutableElement combine) { static String valueType(ExecutableElement init, ExecutableElement combine) {
@ -177,7 +181,7 @@ public class AggregatorImplementer {
case "double" -> DOUBLE_BLOCK; case "double" -> DOUBLE_BLOCK;
case "float" -> FLOAT_BLOCK; case "float" -> FLOAT_BLOCK;
case "long" -> LONG_BLOCK; case "long" -> LONG_BLOCK;
case "int" -> INT_BLOCK; case "int", "int[]" -> INT_BLOCK;
case "org.apache.lucene.util.BytesRef" -> BYTES_REF_BLOCK; case "org.apache.lucene.util.BytesRef" -> BYTES_REF_BLOCK;
default -> throw new IllegalArgumentException("unknown block type for " + valueType(init, combine)); default -> throw new IllegalArgumentException("unknown block type for " + valueType(init, combine));
}; };
@ -189,7 +193,7 @@ public class AggregatorImplementer {
case "double" -> DOUBLE_VECTOR; case "double" -> DOUBLE_VECTOR;
case "float" -> FLOAT_VECTOR; case "float" -> FLOAT_VECTOR;
case "long" -> LONG_VECTOR; case "long" -> LONG_VECTOR;
case "int" -> INT_VECTOR; case "int", "int[]" -> INT_VECTOR;
case "org.apache.lucene.util.BytesRef" -> BYTES_REF_VECTOR; case "org.apache.lucene.util.BytesRef" -> BYTES_REF_VECTOR;
default -> throw new IllegalArgumentException("unknown vector type for " + valueType(init, combine)); default -> throw new IllegalArgumentException("unknown vector type for " + valueType(init, combine));
}; };
@ -390,6 +394,10 @@ public class AggregatorImplementer {
if (masked) { if (masked) {
builder.addParameter(BOOLEAN_VECTOR, "mask"); builder.addParameter(BOOLEAN_VECTOR, "mask");
} }
if (valuesIsArray) {
builder.addComment("This type does not support vectors because all values are multi-valued");
return builder.build();
}
if (stateTypeHasSeen) { if (stateTypeHasSeen) {
builder.addStatement("state.seen(true)"); builder.addStatement("state.seen(true)");
@ -437,10 +445,19 @@ public class AggregatorImplementer {
} }
builder.addStatement("int start = block.getFirstValueIndex(p)"); builder.addStatement("int start = block.getFirstValueIndex(p)");
builder.addStatement("int end = start + block.getValueCount(p)"); builder.addStatement("int end = start + block.getValueCount(p)");
if (valuesIsArray) {
String arrayType = valueTypeString();
builder.addStatement("$L[] valuesArray = new $L[end - start]", arrayType, arrayType);
builder.beginControlFlow("for (int i = start; i < end; i++)");
builder.addStatement("valuesArray[i-start] = $L.get$L(i)", "block", firstUpper(arrayType));
builder.endControlFlow();
combineRawInputForArray(builder, "valuesArray");
} else {
builder.beginControlFlow("for (int i = start; i < end; i++)"); builder.beginControlFlow("for (int i = start; i < end; i++)");
combineRawInput(builder, "block"); combineRawInput(builder, "block");
builder.endControlFlow(); builder.endControlFlow();
} }
}
builder.endControlFlow(); builder.endControlFlow();
if (combineValueCount != null) { if (combineValueCount != null) {
builder.addStatement("$T.combineValueCount(state, block.getTotalValueCount())", declarationType); builder.addStatement("$T.combineValueCount(state, block.getTotalValueCount())", declarationType);
@ -450,9 +467,7 @@ public class AggregatorImplementer {
private void combineRawInput(MethodSpec.Builder builder, String blockVariable) { private void combineRawInput(MethodSpec.Builder builder, String blockVariable) {
TypeName returnType = TypeName.get(combine.getReturnType()); TypeName returnType = TypeName.get(combine.getReturnType());
if (warnExceptions.isEmpty() == false) { warningsBlock(builder, () -> {
builder.beginControlFlow("try");
}
if (valuesIsBytesRef) { if (valuesIsBytesRef) {
combineRawInputForBytesRef(builder, blockVariable); combineRawInputForBytesRef(builder, blockVariable);
} else if (returnType.isPrimitive()) { } else if (returnType.isPrimitive()) {
@ -462,14 +477,7 @@ public class AggregatorImplementer {
} else { } else {
throw new IllegalArgumentException("combine must return void or a primitive"); throw new IllegalArgumentException("combine must return void or a primitive");
} }
if (warnExceptions.isEmpty() == false) { });
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.failed(true)");
builder.addStatement("return");
builder.endControlFlow();
}
} }
private void combineRawInputForPrimitive(TypeName returnType, MethodSpec.Builder builder, String blockVariable) { private void combineRawInputForPrimitive(TypeName returnType, MethodSpec.Builder builder, String blockVariable) {
@ -483,6 +491,10 @@ public class AggregatorImplementer {
); );
} }
private void combineRawInputForArray(MethodSpec.Builder builder, String arrayVariable) {
warningsBlock(builder, () -> builder.addStatement("$T.combine(state, $L)", declarationType, arrayVariable));
}
private void combineRawInputForVoid(MethodSpec.Builder builder, String blockVariable) { private void combineRawInputForVoid(MethodSpec.Builder builder, String blockVariable) {
builder.addStatement( builder.addStatement(
"$T.combine(state, $L.get$L(i))", "$T.combine(state, $L.get$L(i))",
@ -497,6 +509,21 @@ public class AggregatorImplementer {
builder.addStatement("$T.combine(state, $L.getBytesRef(i, scratch))", declarationType, blockVariable); builder.addStatement("$T.combine(state, $L.getBytesRef(i, scratch))", declarationType, blockVariable);
} }
private void warningsBlock(MethodSpec.Builder builder, Runnable block) {
if (warnExceptions.isEmpty() == false) {
builder.beginControlFlow("try");
}
block.run();
if (warnExceptions.isEmpty() == false) {
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.failed(true)");
builder.addStatement("return");
builder.endControlFlow();
}
}
private MethodSpec addIntermediateInput() { private MethodSpec addIntermediateInput() {
MethodSpec.Builder builder = MethodSpec.methodBuilder("addIntermediateInput"); MethodSpec.Builder builder = MethodSpec.methodBuilder("addIntermediateInput");
builder.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addParameter(PAGE, "page"); builder.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).addParameter(PAGE, "page");
@ -529,20 +556,12 @@ public class AggregatorImplementer {
builder.nextControlFlow("else if (seen.getBoolean(0))"); builder.nextControlFlow("else if (seen.getBoolean(0))");
} }
if (warnExceptions.isEmpty() == false) { warningsBlock(builder, () -> {
builder.beginControlFlow("try");
}
var state = intermediateState.get(0); var state = intermediateState.get(0);
var s = "state.$L($T.combine(state.$L(), " + state.name() + "." + vectorAccessorName(state.elementType()) + "(0)))"; var s = "state.$L($T.combine(state.$L(), " + state.name() + "." + vectorAccessorName(state.elementType()) + "(0)))";
builder.addStatement(s, primitiveStateMethod(), declarationType, primitiveStateMethod()); builder.addStatement(s, primitiveStateMethod(), declarationType, primitiveStateMethod());
builder.addStatement("state.seen(true)"); builder.addStatement("state.seen(true)");
if (warnExceptions.isEmpty() == false) { });
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.failed(true)");
builder.endControlFlow();
}
builder.endControlFlow(); builder.endControlFlow();
} else { } else {
throw new IllegalArgumentException("Don't know how to combine intermediate input. Define combineIntermediate"); throw new IllegalArgumentException("Don't know how to combine intermediate input. Define combineIntermediate");
@ -693,4 +712,21 @@ public class AggregatorImplementer {
} }
} }
} }
private TypeMirror valueTypeMirror() {
return combine.getParameters().get(combine.getParameters().size() - 1).asType();
}
private TypeName valueTypeName() {
return TypeName.get(valueTypeMirror());
}
private TypeKind valueTypeKind() {
return valueTypeMirror().getKind();
}
private String valueTypeString() {
String valueTypeString = TypeName.get(valueTypeMirror()).toString();
return valuesIsArray ? valueTypeString.substring(0, valueTypeString.length() - 2) : valueTypeString;
}
} }

View file

@ -20,7 +20,6 @@ import org.elasticsearch.compute.ann.IntermediateState;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -28,10 +27,12 @@ import java.util.stream.Collectors;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
import static org.elasticsearch.compute.gen.AggregatorImplementer.firstUpper;
import static org.elasticsearch.compute.gen.AggregatorImplementer.valueBlockType; import static org.elasticsearch.compute.gen.AggregatorImplementer.valueBlockType;
import static org.elasticsearch.compute.gen.AggregatorImplementer.valueVectorType; import static org.elasticsearch.compute.gen.AggregatorImplementer.valueVectorType;
import static org.elasticsearch.compute.gen.Methods.findMethod; import static org.elasticsearch.compute.gen.Methods.findMethod;
@ -74,6 +75,7 @@ public class GroupingAggregatorImplementer {
private final ExecutableElement combineIntermediate; private final ExecutableElement combineIntermediate;
private final TypeName stateType; private final TypeName stateType;
private final boolean valuesIsBytesRef; private final boolean valuesIsBytesRef;
private final boolean valuesIsArray;
private final List<Parameter> createParameters; private final List<Parameter> createParameters;
private final ClassName implementation; private final ClassName implementation;
private final List<AggregatorImplementer.IntermediateStateDesc> intermediateState; private final List<AggregatorImplementer.IntermediateStateDesc> intermediateState;
@ -102,7 +104,8 @@ public class GroupingAggregatorImplementer {
this.combineStates = findMethod(declarationType, "combineStates"); this.combineStates = findMethod(declarationType, "combineStates");
this.combineIntermediate = findMethod(declarationType, "combineIntermediate"); this.combineIntermediate = findMethod(declarationType, "combineIntermediate");
this.evaluateFinal = findMethod(declarationType, "evaluateFinal"); this.evaluateFinal = findMethod(declarationType, "evaluateFinal");
this.valuesIsBytesRef = BYTES_REF.equals(TypeName.get(combine.getParameters().get(combine.getParameters().size() - 1).asType())); this.valuesIsBytesRef = BYTES_REF.equals(valueTypeName());
this.valuesIsArray = TypeKind.ARRAY.equals(valueTypeKind());
this.createParameters = init.getParameters() this.createParameters = init.getParameters()
.stream() .stream()
.map(Parameter::from) .map(Parameter::from)
@ -133,12 +136,11 @@ public class GroupingAggregatorImplementer {
if (false == initReturn.isPrimitive()) { if (false == initReturn.isPrimitive()) {
return initReturn; return initReturn;
} }
String head = initReturn.toString().substring(0, 1).toUpperCase(Locale.ROOT); String simpleName = firstUpper(initReturn.toString());
String tail = initReturn.toString().substring(1);
if (warnExceptions.isEmpty()) { if (warnExceptions.isEmpty()) {
return ClassName.get("org.elasticsearch.compute.aggregation", head + tail + "ArrayState"); return ClassName.get("org.elasticsearch.compute.aggregation", simpleName + "ArrayState");
} }
return ClassName.get("org.elasticsearch.compute.aggregation", head + tail + "FallibleArrayState"); return ClassName.get("org.elasticsearch.compute.aggregation", simpleName + "FallibleArrayState");
} }
public JavaFile sourceFile() { public JavaFile sourceFile() {
@ -364,6 +366,10 @@ public class GroupingAggregatorImplementer {
// Add bytes_ref scratch var that will be used for bytes_ref blocks/vectors // Add bytes_ref scratch var that will be used for bytes_ref blocks/vectors
builder.addStatement("$T scratch = new $T()", BYTES_REF, BYTES_REF); builder.addStatement("$T scratch = new $T()", BYTES_REF, BYTES_REF);
} }
if (valuesIsArray && valuesIsBlock == false) {
builder.addComment("This type does not support vectors because all values are multi-valued");
return builder.build();
}
builder.beginControlFlow("for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++)"); builder.beginControlFlow("for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++)");
{ {
@ -391,9 +397,18 @@ public class GroupingAggregatorImplementer {
builder.endControlFlow(); builder.endControlFlow();
builder.addStatement("int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset)"); builder.addStatement("int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset)");
builder.addStatement("int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset)"); builder.addStatement("int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset)");
if (valuesIsArray) {
String arrayType = valueTypeString();
builder.addStatement("$L[] valuesArray = new $L[valuesEnd - valuesStart]", arrayType, arrayType);
builder.beginControlFlow("for (int v = valuesStart; v < valuesEnd; v++)");
builder.addStatement("valuesArray[v-valuesStart] = $L.get$L(v)", "values", firstUpper(arrayType));
builder.endControlFlow();
combineRawInputForArray(builder, "valuesArray");
} else {
builder.beginControlFlow("for (int v = valuesStart; v < valuesEnd; v++)"); builder.beginControlFlow("for (int v = valuesStart; v < valuesEnd; v++)");
combineRawInput(builder, "values", "v"); combineRawInput(builder, "values", "v");
builder.endControlFlow(); builder.endControlFlow();
}
} else { } else {
combineRawInput(builder, "values", "groupPosition + positionOffset"); combineRawInput(builder, "values", "groupPosition + positionOffset");
} }
@ -407,70 +422,52 @@ public class GroupingAggregatorImplementer {
} }
private void combineRawInput(MethodSpec.Builder builder, String blockVariable, String offsetVariable) { private void combineRawInput(MethodSpec.Builder builder, String blockVariable, String offsetVariable) {
TypeName valueType = TypeName.get(combine.getParameters().get(combine.getParameters().size() - 1).asType()); TypeName valueType = valueTypeName();
String secondParameterGetter = "get"
+ valueType.toString().substring(0, 1).toUpperCase(Locale.ROOT)
+ valueType.toString().substring(1);
TypeName returnType = TypeName.get(combine.getReturnType()); TypeName returnType = TypeName.get(combine.getReturnType());
if (warnExceptions.isEmpty() == false) { warningsBlock(builder, () -> {
builder.beginControlFlow("try");
}
if (valuesIsBytesRef) { if (valuesIsBytesRef) {
combineRawInputForBytesRef(builder, blockVariable, offsetVariable); combineRawInputForBytesRef(builder, blockVariable, offsetVariable);
} else if (includeTimestampVector) { } else if (includeTimestampVector) {
combineRawInputWithTimestamp(builder, offsetVariable); combineRawInputWithTimestamp(builder, offsetVariable);
} else if (valueType.isPrimitive() == false) { } else if (valueType.isPrimitive() == false) {
throw new IllegalArgumentException("second parameter to combine must be a primitive"); throw new IllegalArgumentException("second parameter to combine must be a primitive, array or BytesRef: " + valueType);
} else if (returnType.isPrimitive()) { } else if (returnType.isPrimitive()) {
combineRawInputForPrimitive(builder, secondParameterGetter, blockVariable, offsetVariable); combineRawInputForPrimitive(builder, blockVariable, offsetVariable);
} else if (returnType == TypeName.VOID) { } else if (returnType == TypeName.VOID) {
combineRawInputForVoid(builder, secondParameterGetter, blockVariable, offsetVariable); combineRawInputForVoid(builder, blockVariable, offsetVariable);
} else { } else {
throw new IllegalArgumentException("combine must return void or a primitive"); throw new IllegalArgumentException("combine must return void or a primitive");
} }
if (warnExceptions.isEmpty() == false) { });
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.setFailed(groupId)");
builder.endControlFlow();
}
} }
private void combineRawInputForPrimitive( private void combineRawInputForPrimitive(MethodSpec.Builder builder, String blockVariable, String offsetVariable) {
MethodSpec.Builder builder,
String secondParameterGetter,
String blockVariable,
String offsetVariable
) {
builder.addStatement( builder.addStatement(
"state.set(groupId, $T.combine(state.getOrDefault(groupId), $L.$L($L)))", "state.set(groupId, $T.combine(state.getOrDefault(groupId), $L.get$L($L)))",
declarationType, declarationType,
blockVariable, blockVariable,
secondParameterGetter, firstUpper(valueTypeName().toString()),
offsetVariable offsetVariable
); );
} }
private void combineRawInputForVoid( private void combineRawInputForArray(MethodSpec.Builder builder, String arrayVariable) {
MethodSpec.Builder builder, warningsBlock(builder, () -> builder.addStatement("$T.combine(state, groupId, $L)", declarationType, arrayVariable));
String secondParameterGetter, }
String blockVariable,
String offsetVariable private void combineRawInputForVoid(MethodSpec.Builder builder, String blockVariable, String offsetVariable) {
) {
builder.addStatement( builder.addStatement(
"$T.combine(state, groupId, $L.$L($L))", "$T.combine(state, groupId, $L.get$L($L))",
declarationType, declarationType,
blockVariable, blockVariable,
secondParameterGetter, firstUpper(valueTypeName().toString()),
offsetVariable offsetVariable
); );
} }
private void combineRawInputWithTimestamp(MethodSpec.Builder builder, String offsetVariable) { private void combineRawInputWithTimestamp(MethodSpec.Builder builder, String offsetVariable) {
TypeName valueType = TypeName.get(combine.getParameters().get(combine.getParameters().size() - 1).asType()); String blockType = firstUpper(valueTypeName().toString());
String blockType = valueType.toString().substring(0, 1).toUpperCase(Locale.ROOT) + valueType.toString().substring(1);
if (offsetVariable.contains(" + ")) { if (offsetVariable.contains(" + ")) {
builder.addStatement("var valuePosition = $L", offsetVariable); builder.addStatement("var valuePosition = $L", offsetVariable);
offsetVariable = "valuePosition"; offsetVariable = "valuePosition";
@ -489,6 +486,20 @@ public class GroupingAggregatorImplementer {
builder.addStatement("$T.combine(state, groupId, $L.getBytesRef($L, scratch))", declarationType, blockVariable, offsetVariable); builder.addStatement("$T.combine(state, groupId, $L.getBytesRef($L, scratch))", declarationType, blockVariable, offsetVariable);
} }
private void warningsBlock(MethodSpec.Builder builder, Runnable block) {
if (warnExceptions.isEmpty() == false) {
builder.beginControlFlow("try");
}
block.run();
if (warnExceptions.isEmpty() == false) {
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.setFailed(groupId)");
builder.endControlFlow();
}
}
private MethodSpec selectedMayContainUnseenGroups() { private MethodSpec selectedMayContainUnseenGroups() {
MethodSpec.Builder builder = MethodSpec.methodBuilder("selectedMayContainUnseenGroups"); MethodSpec.Builder builder = MethodSpec.methodBuilder("selectedMayContainUnseenGroups");
builder.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC); builder.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC);
@ -544,9 +555,7 @@ public class GroupingAggregatorImplementer {
builder.nextControlFlow("else if (seen.getBoolean(groupPosition + positionOffset))"); builder.nextControlFlow("else if (seen.getBoolean(groupPosition + positionOffset))");
} }
if (warnExceptions.isEmpty() == false) { warningsBlock(builder, () -> {
builder.beginControlFlow("try");
}
var name = intermediateState.get(0).name(); var name = intermediateState.get(0).name();
var vectorAccessor = vectorAccessorName(intermediateState.get(0).elementType()); var vectorAccessor = vectorAccessorName(intermediateState.get(0).elementType());
builder.addStatement( builder.addStatement(
@ -555,13 +564,7 @@ public class GroupingAggregatorImplementer {
name, name,
vectorAccessor vectorAccessor
); );
if (warnExceptions.isEmpty() == false) { });
String catchPattern = "catch (" + warnExceptions.stream().map(m -> "$T").collect(Collectors.joining(" | ")) + " e)";
builder.nextControlFlow(catchPattern, warnExceptions.stream().map(TypeName::get).toArray());
builder.addStatement("warnings.registerException(e)");
builder.addStatement("state.setFailed(groupId)");
builder.endControlFlow();
}
builder.endControlFlow(); builder.endControlFlow();
} else { } else {
builder.addStatement("$T.combineIntermediate(state, groupId, " + intermediateStateRowAccess() + ")", declarationType); builder.addStatement("$T.combineIntermediate(state, groupId, " + intermediateStateRowAccess() + ")", declarationType);
@ -657,4 +660,24 @@ public class GroupingAggregatorImplementer {
private boolean hasPrimitiveState() { private boolean hasPrimitiveState() {
return PRIMITIVE_STATE_PATTERN.matcher(stateType.toString()).matches(); return PRIMITIVE_STATE_PATTERN.matcher(stateType.toString()).matches();
} }
private TypeMirror valueTypeMirror() {
return combine.getParameters().get(combine.getParameters().size() - 1).asType();
}
private TypeName valueTypeName() {
return TypeName.get(valueTypeMirror());
}
private TypeKind valueTypeKind() {
return valueTypeMirror().getKind();
}
private String valueTypeString() {
String valueTypeString = TypeName.get(valueTypeMirror()).toString();
if (valuesIsArray) {
valueTypeString = valueTypeString.substring(0, valueTypeString.length() - 2);
}
return valueTypeString;
}
} }

View file

@ -0,0 +1,182 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import org.elasticsearch.compute.aggregation.AggregatorFunction;
import org.elasticsearch.compute.aggregation.IntermediateStateDesc;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BooleanVector;
import org.elasticsearch.compute.data.ElementType;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.compute.data.IntVector;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link AggregatorFunction} implementation for {@link SpatialExtentCartesianShapeDocValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentCartesianShapeDocValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minX", ElementType.INT),
new IntermediateStateDesc("maxX", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) );
private final DriverContext driverContext;
private final SpatialExtentState state;
private final List<Integer> channels;
public SpatialExtentCartesianShapeDocValuesAggregatorFunction(DriverContext driverContext,
List<Integer> channels, SpatialExtentState state) {
this.driverContext = driverContext;
this.channels = channels;
this.state = state;
}
public static SpatialExtentCartesianShapeDocValuesAggregatorFunction create(
DriverContext driverContext, List<Integer> channels) {
return new SpatialExtentCartesianShapeDocValuesAggregatorFunction(driverContext, channels, SpatialExtentCartesianShapeDocValuesAggregator.initSingle());
}
public static List<IntermediateStateDesc> intermediateStateDesc() {
return INTERMEDIATE_STATE_DESC;
}
@Override
public int intermediateBlockCount() {
return INTERMEDIATE_STATE_DESC.size();
}
@Override
public void addRawInput(Page page, BooleanVector mask) {
if (mask.allFalse()) {
// Entire page masked away
return;
}
if (mask.allTrue()) {
// No masking
IntBlock block = page.getBlock(channels.get(0));
IntVector vector = block.asVector();
if (vector != null) {
addRawVector(vector);
} else {
addRawBlock(block);
}
return;
}
// Some positions masked away, others kept
IntBlock block = page.getBlock(channels.get(0));
IntVector vector = block.asVector();
if (vector != null) {
addRawVector(vector, mask);
} else {
addRawBlock(block, mask);
}
}
private void addRawVector(IntVector vector) {
// This type does not support vectors because all values are multi-valued
}
private void addRawVector(IntVector vector, BooleanVector mask) {
// This type does not support vectors because all values are multi-valued
}
private void addRawBlock(IntBlock block) {
for (int p = 0; p < block.getPositionCount(); p++) {
if (block.isNull(p)) {
continue;
}
int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p);
int[] valuesArray = new int[end - start];
for (int i = start; i < end; i++) {
valuesArray[i-start] = block.getInt(i);
}
SpatialExtentCartesianShapeDocValuesAggregator.combine(state, valuesArray);
}
}
private void addRawBlock(IntBlock block, BooleanVector mask) {
for (int p = 0; p < block.getPositionCount(); p++) {
if (mask.getBoolean(p) == false) {
continue;
}
if (block.isNull(p)) {
continue;
}
int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p);
int[] valuesArray = new int[end - start];
for (int i = start; i < end; i++) {
valuesArray[i-start] = block.getInt(i);
}
SpatialExtentCartesianShapeDocValuesAggregator.combine(state, valuesArray);
}
}
@Override
public void addIntermediateInput(Page page) {
assert channels.size() == intermediateBlockCount();
assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size();
Block minXUncast = page.getBlock(channels.get(0));
if (minXUncast.areAllValuesNull()) {
return;
}
IntVector minX = ((IntBlock) minXUncast).asVector();
assert minX.getPositionCount() == 1;
Block maxXUncast = page.getBlock(channels.get(1));
if (maxXUncast.areAllValuesNull()) {
return;
}
IntVector maxX = ((IntBlock) maxXUncast).asVector();
assert maxX.getPositionCount() == 1;
Block maxYUncast = page.getBlock(channels.get(2));
if (maxYUncast.areAllValuesNull()) {
return;
}
IntVector maxY = ((IntBlock) maxYUncast).asVector();
assert maxY.getPositionCount() == 1;
Block minYUncast = page.getBlock(channels.get(3));
if (minYUncast.areAllValuesNull()) {
return;
}
IntVector minY = ((IntBlock) minYUncast).asVector();
assert minY.getPositionCount() == 1;
SpatialExtentCartesianShapeDocValuesAggregator.combineIntermediate(state, minX.getInt(0), maxX.getInt(0), maxY.getInt(0), minY.getInt(0));
}
@Override
public void evaluateIntermediate(Block[] blocks, int offset, DriverContext driverContext) {
state.toIntermediate(blocks, offset, driverContext);
}
@Override
public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) {
blocks[offset] = SpatialExtentCartesianShapeDocValuesAggregator.evaluateFinal(state, driverContext);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append("[");
sb.append("channels=").append(channels);
sb.append("]");
return sb.toString();
}
@Override
public void close() {
state.close();
}
}

View file

@ -0,0 +1,41 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.util.List;
import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentCartesianShapeDocValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentCartesianShapeDocValuesAggregatorFunctionSupplier implements AggregatorFunctionSupplier {
private final List<Integer> channels;
public SpatialExtentCartesianShapeDocValuesAggregatorFunctionSupplier(List<Integer> channels) {
this.channels = channels;
}
@Override
public SpatialExtentCartesianShapeDocValuesAggregatorFunction aggregator(
DriverContext driverContext) {
return SpatialExtentCartesianShapeDocValuesAggregatorFunction.create(driverContext, channels);
}
@Override
public SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction groupingAggregator(
DriverContext driverContext) {
return SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction.create(channels, driverContext);
}
@Override
public String describe() {
return "spatial_extent_cartesian_shape_doc of valuess";
}
}

View file

@ -0,0 +1,219 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction;
import org.elasticsearch.compute.aggregation.IntermediateStateDesc;
import org.elasticsearch.compute.aggregation.SeenGroupIds;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.ElementType;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.compute.data.IntVector;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentCartesianShapeDocValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minX", ElementType.INT),
new IntermediateStateDesc("maxX", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) );
private final SpatialExtentGroupingState state;
private final List<Integer> channels;
private final DriverContext driverContext;
public SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction(List<Integer> channels,
SpatialExtentGroupingState state, DriverContext driverContext) {
this.channels = channels;
this.state = state;
this.driverContext = driverContext;
}
public static SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction create(
List<Integer> channels, DriverContext driverContext) {
return new SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction(channels, SpatialExtentCartesianShapeDocValuesAggregator.initGrouping(), driverContext);
}
public static List<IntermediateStateDesc> intermediateStateDesc() {
return INTERMEDIATE_STATE_DESC;
}
@Override
public int intermediateBlockCount() {
return INTERMEDIATE_STATE_DESC.size();
}
@Override
public GroupingAggregatorFunction.AddInput prepareProcessPage(SeenGroupIds seenGroupIds,
Page page) {
IntBlock valuesBlock = page.getBlock(channels.get(0));
IntVector valuesVector = valuesBlock.asVector();
if (valuesVector == null) {
if (valuesBlock.mayHaveNulls()) {
state.enableGroupIdTracking(seenGroupIds);
}
return new GroupingAggregatorFunction.AddInput() {
@Override
public void add(int positionOffset, IntBlock groupIds) {
addRawInput(positionOffset, groupIds, valuesBlock);
}
@Override
public void add(int positionOffset, IntVector groupIds) {
addRawInput(positionOffset, groupIds, valuesBlock);
}
@Override
public void close() {
}
};
}
return new GroupingAggregatorFunction.AddInput() {
@Override
public void add(int positionOffset, IntBlock groupIds) {
addRawInput(positionOffset, groupIds, valuesVector);
}
@Override
public void add(int positionOffset, IntVector groupIds) {
addRawInput(positionOffset, groupIds, valuesVector);
}
@Override
public void close() {
}
};
}
private void addRawInput(int positionOffset, IntVector groups, IntBlock values) {
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition);
if (values.isNull(groupPosition + positionOffset)) {
continue;
}
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
int[] valuesArray = new int[valuesEnd - valuesStart];
for (int v = valuesStart; v < valuesEnd; v++) {
valuesArray[v-valuesStart] = values.getInt(v);
}
SpatialExtentCartesianShapeDocValuesAggregator.combine(state, groupId, valuesArray);
}
}
private void addRawInput(int positionOffset, IntVector groups, IntVector values) {
// This type does not support vectors because all values are multi-valued
}
private void addRawInput(int positionOffset, IntBlock groups, IntBlock values) {
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
if (groups.isNull(groupPosition)) {
continue;
}
int groupStart = groups.getFirstValueIndex(groupPosition);
int groupEnd = groupStart + groups.getValueCount(groupPosition);
for (int g = groupStart; g < groupEnd; g++) {
int groupId = groups.getInt(g);
if (values.isNull(groupPosition + positionOffset)) {
continue;
}
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
int[] valuesArray = new int[valuesEnd - valuesStart];
for (int v = valuesStart; v < valuesEnd; v++) {
valuesArray[v-valuesStart] = values.getInt(v);
}
SpatialExtentCartesianShapeDocValuesAggregator.combine(state, groupId, valuesArray);
}
}
}
private void addRawInput(int positionOffset, IntBlock groups, IntVector values) {
// This type does not support vectors because all values are multi-valued
}
@Override
public void selectedMayContainUnseenGroups(SeenGroupIds seenGroupIds) {
state.enableGroupIdTracking(seenGroupIds);
}
@Override
public void addIntermediateInput(int positionOffset, IntVector groups, Page page) {
state.enableGroupIdTracking(new SeenGroupIds.Empty());
assert channels.size() == intermediateBlockCount();
Block minXUncast = page.getBlock(channels.get(0));
if (minXUncast.areAllValuesNull()) {
return;
}
IntVector minX = ((IntBlock) minXUncast).asVector();
Block maxXUncast = page.getBlock(channels.get(1));
if (maxXUncast.areAllValuesNull()) {
return;
}
IntVector maxX = ((IntBlock) maxXUncast).asVector();
Block maxYUncast = page.getBlock(channels.get(2));
if (maxYUncast.areAllValuesNull()) {
return;
}
IntVector maxY = ((IntBlock) maxYUncast).asVector();
Block minYUncast = page.getBlock(channels.get(3));
if (minYUncast.areAllValuesNull()) {
return;
}
IntVector minY = ((IntBlock) minYUncast).asVector();
assert minX.getPositionCount() == maxX.getPositionCount() && minX.getPositionCount() == maxY.getPositionCount() && minX.getPositionCount() == minY.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition);
SpatialExtentCartesianShapeDocValuesAggregator.combineIntermediate(state, groupId, minX.getInt(groupPosition + positionOffset), maxX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset));
}
}
@Override
public void addIntermediateRowInput(int groupId, GroupingAggregatorFunction input, int position) {
if (input.getClass() != getClass()) {
throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass());
}
SpatialExtentGroupingState inState = ((SpatialExtentCartesianShapeDocValuesGroupingAggregatorFunction) input).state;
state.enableGroupIdTracking(new SeenGroupIds.Empty());
SpatialExtentCartesianShapeDocValuesAggregator.combineStates(state, groupId, inState, position);
}
@Override
public void evaluateIntermediate(Block[] blocks, int offset, IntVector selected) {
state.toIntermediate(blocks, offset, selected, driverContext);
}
@Override
public void evaluateFinal(Block[] blocks, int offset, IntVector selected,
DriverContext driverContext) {
blocks[offset] = SpatialExtentCartesianShapeDocValuesAggregator.evaluateFinal(state, selected, driverContext);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append("[");
sb.append("channels=").append(channels);
sb.append("]");
return sb.toString();
}
@Override
public void close() {
state.close();
}
}

View file

@ -23,10 +23,10 @@ import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link AggregatorFunction} implementation for {@link SpatialExtentCartesianShapeAggregator}. * {@link AggregatorFunction} implementation for {@link SpatialExtentCartesianShapeSourceValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentCartesianShapeAggregatorFunction implements AggregatorFunction { public final class SpatialExtentCartesianShapeSourceValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minX", ElementType.INT), new IntermediateStateDesc("minX", ElementType.INT),
new IntermediateStateDesc("maxX", ElementType.INT), new IntermediateStateDesc("maxX", ElementType.INT),
@ -39,16 +39,16 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
private final List<Integer> channels; private final List<Integer> channels;
public SpatialExtentCartesianShapeAggregatorFunction(DriverContext driverContext, public SpatialExtentCartesianShapeSourceValuesAggregatorFunction(DriverContext driverContext,
List<Integer> channels, SpatialExtentState state) { List<Integer> channels, SpatialExtentState state) {
this.driverContext = driverContext; this.driverContext = driverContext;
this.channels = channels; this.channels = channels;
this.state = state; this.state = state;
} }
public static SpatialExtentCartesianShapeAggregatorFunction create(DriverContext driverContext, public static SpatialExtentCartesianShapeSourceValuesAggregatorFunction create(
List<Integer> channels) { DriverContext driverContext, List<Integer> channels) {
return new SpatialExtentCartesianShapeAggregatorFunction(driverContext, channels, SpatialExtentCartesianShapeAggregator.initSingle()); return new SpatialExtentCartesianShapeSourceValuesAggregatorFunction(driverContext, channels, SpatialExtentCartesianShapeSourceValuesAggregator.initSingle());
} }
public static List<IntermediateStateDesc> intermediateStateDesc() { public static List<IntermediateStateDesc> intermediateStateDesc() {
@ -90,7 +90,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
private void addRawVector(BytesRefVector vector) { private void addRawVector(BytesRefVector vector) {
BytesRef scratch = new BytesRef(); BytesRef scratch = new BytesRef();
for (int i = 0; i < vector.getPositionCount(); i++) { for (int i = 0; i < vector.getPositionCount(); i++) {
SpatialExtentCartesianShapeAggregator.combine(state, vector.getBytesRef(i, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, vector.getBytesRef(i, scratch));
} }
} }
@ -100,7 +100,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
if (mask.getBoolean(i) == false) { if (mask.getBoolean(i) == false) {
continue; continue;
} }
SpatialExtentCartesianShapeAggregator.combine(state, vector.getBytesRef(i, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, vector.getBytesRef(i, scratch));
} }
} }
@ -113,7 +113,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
int start = block.getFirstValueIndex(p); int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p); int end = start + block.getValueCount(p);
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
SpatialExtentCartesianShapeAggregator.combine(state, block.getBytesRef(i, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, block.getBytesRef(i, scratch));
} }
} }
} }
@ -130,7 +130,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
int start = block.getFirstValueIndex(p); int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p); int end = start + block.getValueCount(p);
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
SpatialExtentCartesianShapeAggregator.combine(state, block.getBytesRef(i, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, block.getBytesRef(i, scratch));
} }
} }
} }
@ -163,7 +163,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector minY = ((IntBlock) minYUncast).asVector();
assert minY.getPositionCount() == 1; assert minY.getPositionCount() == 1;
SpatialExtentCartesianShapeAggregator.combineIntermediate(state, minX.getInt(0), maxX.getInt(0), maxY.getInt(0), minY.getInt(0)); SpatialExtentCartesianShapeSourceValuesAggregator.combineIntermediate(state, minX.getInt(0), maxX.getInt(0), maxY.getInt(0), minY.getInt(0));
} }
@Override @Override
@ -173,7 +173,7 @@ public final class SpatialExtentCartesianShapeAggregatorFunction implements Aggr
@Override @Override
public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) { public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) {
blocks[offset] = SpatialExtentCartesianShapeAggregator.evaluateFinal(state, driverContext); blocks[offset] = SpatialExtentCartesianShapeSourceValuesAggregator.evaluateFinal(state, driverContext);
} }
@Override @Override

View file

@ -0,0 +1,41 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.util.List;
import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentCartesianShapeSourceValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentCartesianShapeSourceValuesAggregatorFunctionSupplier implements AggregatorFunctionSupplier {
private final List<Integer> channels;
public SpatialExtentCartesianShapeSourceValuesAggregatorFunctionSupplier(List<Integer> channels) {
this.channels = channels;
}
@Override
public SpatialExtentCartesianShapeSourceValuesAggregatorFunction aggregator(
DriverContext driverContext) {
return SpatialExtentCartesianShapeSourceValuesAggregatorFunction.create(driverContext, channels);
}
@Override
public SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction groupingAggregator(
DriverContext driverContext) {
return SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction.create(channels, driverContext);
}
@Override
public String describe() {
return "spatial_extent_cartesian_shape_source of valuess";
}
}

View file

@ -23,10 +23,10 @@ import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentCartesianShapeAggregator}. * {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentCartesianShapeSourceValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentCartesianShapeGroupingAggregatorFunction implements GroupingAggregatorFunction { public final class SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minX", ElementType.INT), new IntermediateStateDesc("minX", ElementType.INT),
new IntermediateStateDesc("maxX", ElementType.INT), new IntermediateStateDesc("maxX", ElementType.INT),
@ -39,16 +39,16 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
private final DriverContext driverContext; private final DriverContext driverContext;
public SpatialExtentCartesianShapeGroupingAggregatorFunction(List<Integer> channels, public SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction(List<Integer> channels,
SpatialExtentGroupingState state, DriverContext driverContext) { SpatialExtentGroupingState state, DriverContext driverContext) {
this.channels = channels; this.channels = channels;
this.state = state; this.state = state;
this.driverContext = driverContext; this.driverContext = driverContext;
} }
public static SpatialExtentCartesianShapeGroupingAggregatorFunction create(List<Integer> channels, public static SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction create(
DriverContext driverContext) { List<Integer> channels, DriverContext driverContext) {
return new SpatialExtentCartesianShapeGroupingAggregatorFunction(channels, SpatialExtentCartesianShapeAggregator.initGrouping(), driverContext); return new SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction(channels, SpatialExtentCartesianShapeSourceValuesAggregator.initGrouping(), driverContext);
} }
public static List<IntermediateStateDesc> intermediateStateDesc() { public static List<IntermediateStateDesc> intermediateStateDesc() {
@ -112,7 +112,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset); int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset); int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
for (int v = valuesStart; v < valuesEnd; v++) { for (int v = valuesStart; v < valuesEnd; v++) {
SpatialExtentCartesianShapeAggregator.combine(state, groupId, values.getBytesRef(v, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(v, scratch));
} }
} }
} }
@ -121,7 +121,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
BytesRef scratch = new BytesRef(); BytesRef scratch = new BytesRef();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentCartesianShapeAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch));
} }
} }
@ -141,7 +141,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset); int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset); int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
for (int v = valuesStart; v < valuesEnd; v++) { for (int v = valuesStart; v < valuesEnd; v++) {
SpatialExtentCartesianShapeAggregator.combine(state, groupId, values.getBytesRef(v, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(v, scratch));
} }
} }
} }
@ -157,7 +157,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
int groupEnd = groupStart + groups.getValueCount(groupPosition); int groupEnd = groupStart + groups.getValueCount(groupPosition);
for (int g = groupStart; g < groupEnd; g++) { for (int g = groupStart; g < groupEnd; g++) {
int groupId = groups.getInt(g); int groupId = groups.getInt(g);
SpatialExtentCartesianShapeAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch)); SpatialExtentCartesianShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch));
} }
} }
} }
@ -194,7 +194,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
assert minX.getPositionCount() == maxX.getPositionCount() && minX.getPositionCount() == maxY.getPositionCount() && minX.getPositionCount() == minY.getPositionCount(); assert minX.getPositionCount() == maxX.getPositionCount() && minX.getPositionCount() == maxY.getPositionCount() && minX.getPositionCount() == minY.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentCartesianShapeAggregator.combineIntermediate(state, groupId, minX.getInt(groupPosition + positionOffset), maxX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset)); SpatialExtentCartesianShapeSourceValuesAggregator.combineIntermediate(state, groupId, minX.getInt(groupPosition + positionOffset), maxX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset));
} }
} }
@ -203,9 +203,9 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
if (input.getClass() != getClass()) { if (input.getClass() != getClass()) {
throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass()); throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass());
} }
SpatialExtentGroupingState inState = ((SpatialExtentCartesianShapeGroupingAggregatorFunction) input).state; SpatialExtentGroupingState inState = ((SpatialExtentCartesianShapeSourceValuesGroupingAggregatorFunction) input).state;
state.enableGroupIdTracking(new SeenGroupIds.Empty()); state.enableGroupIdTracking(new SeenGroupIds.Empty());
SpatialExtentCartesianShapeAggregator.combineStates(state, groupId, inState, position); SpatialExtentCartesianShapeSourceValuesAggregator.combineStates(state, groupId, inState, position);
} }
@Override @Override
@ -216,7 +216,7 @@ public final class SpatialExtentCartesianShapeGroupingAggregatorFunction impleme
@Override @Override
public void evaluateFinal(Block[] blocks, int offset, IntVector selected, public void evaluateFinal(Block[] blocks, int offset, IntVector selected,
DriverContext driverContext) { DriverContext driverContext) {
blocks[offset] = SpatialExtentCartesianShapeAggregator.evaluateFinal(state, selected, driverContext); blocks[offset] = SpatialExtentCartesianShapeSourceValuesAggregator.evaluateFinal(state, selected, driverContext);
} }
@Override @Override

View file

@ -27,12 +27,12 @@ import org.elasticsearch.compute.operator.DriverContext;
*/ */
public final class SpatialExtentGeoPointDocValuesAggregatorFunction implements AggregatorFunction { public final class SpatialExtentGeoPointDocValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final DriverContext driverContext; private final DriverContext driverContext;
@ -136,43 +136,43 @@ public final class SpatialExtentGeoPointDocValuesAggregatorFunction implements A
public void addIntermediateInput(Page page) { public void addIntermediateInput(Page page) {
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size(); assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
assert minNegX.getPositionCount() == 1; assert top.getPositionCount() == 1;
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
assert minPosX.getPositionCount() == 1; assert bottom.getPositionCount() == 1;
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
assert maxNegX.getPositionCount() == 1; assert negLeft.getPositionCount() == 1;
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
assert maxPosX.getPositionCount() == 1; assert negRight.getPositionCount() == 1;
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
assert maxY.getPositionCount() == 1; assert posLeft.getPositionCount() == 1;
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minY.getPositionCount() == 1; assert posRight.getPositionCount() == 1;
SpatialExtentGeoPointDocValuesAggregator.combineIntermediate(state, minNegX.getInt(0), minPosX.getInt(0), maxNegX.getInt(0), maxPosX.getInt(0), maxY.getInt(0), minY.getInt(0)); SpatialExtentGeoPointDocValuesAggregator.combineIntermediate(state, top.getInt(0), bottom.getInt(0), negLeft.getInt(0), negRight.getInt(0), posLeft.getInt(0), posRight.getInt(0));
} }
@Override @Override

View file

@ -27,12 +27,12 @@ import org.elasticsearch.compute.operator.DriverContext;
*/ */
public final class SpatialExtentGeoPointDocValuesGroupingAggregatorFunction implements GroupingAggregatorFunction { public final class SpatialExtentGeoPointDocValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final SpatialExtentGroupingStateWrappedLongitudeState state; private final SpatialExtentGroupingStateWrappedLongitudeState state;
@ -168,40 +168,40 @@ public final class SpatialExtentGeoPointDocValuesGroupingAggregatorFunction impl
public void addIntermediateInput(int positionOffset, IntVector groups, Page page) { public void addIntermediateInput(int positionOffset, IntVector groups, Page page) {
state.enableGroupIdTracking(new SeenGroupIds.Empty()); state.enableGroupIdTracking(new SeenGroupIds.Empty());
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minNegX.getPositionCount() == minPosX.getPositionCount() && minNegX.getPositionCount() == maxNegX.getPositionCount() && minNegX.getPositionCount() == maxPosX.getPositionCount() && minNegX.getPositionCount() == maxY.getPositionCount() && minNegX.getPositionCount() == minY.getPositionCount(); assert top.getPositionCount() == bottom.getPositionCount() && top.getPositionCount() == negLeft.getPositionCount() && top.getPositionCount() == negRight.getPositionCount() && top.getPositionCount() == posLeft.getPositionCount() && top.getPositionCount() == posRight.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentGeoPointDocValuesAggregator.combineIntermediate(state, groupId, minNegX.getInt(groupPosition + positionOffset), minPosX.getInt(groupPosition + positionOffset), maxNegX.getInt(groupPosition + positionOffset), maxPosX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset)); SpatialExtentGeoPointDocValuesAggregator.combineIntermediate(state, groupId, top.getInt(groupPosition + positionOffset), bottom.getInt(groupPosition + positionOffset), negLeft.getInt(groupPosition + positionOffset), negRight.getInt(groupPosition + positionOffset), posLeft.getInt(groupPosition + positionOffset), posRight.getInt(groupPosition + positionOffset));
} }
} }

View file

@ -28,12 +28,12 @@ import org.elasticsearch.compute.operator.DriverContext;
*/ */
public final class SpatialExtentGeoPointSourceValuesAggregatorFunction implements AggregatorFunction { public final class SpatialExtentGeoPointSourceValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final DriverContext driverContext; private final DriverContext driverContext;
@ -141,43 +141,43 @@ public final class SpatialExtentGeoPointSourceValuesAggregatorFunction implement
public void addIntermediateInput(Page page) { public void addIntermediateInput(Page page) {
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size(); assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
assert minNegX.getPositionCount() == 1; assert top.getPositionCount() == 1;
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
assert minPosX.getPositionCount() == 1; assert bottom.getPositionCount() == 1;
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
assert maxNegX.getPositionCount() == 1; assert negLeft.getPositionCount() == 1;
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
assert maxPosX.getPositionCount() == 1; assert negRight.getPositionCount() == 1;
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
assert maxY.getPositionCount() == 1; assert posLeft.getPositionCount() == 1;
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minY.getPositionCount() == 1; assert posRight.getPositionCount() == 1;
SpatialExtentGeoPointSourceValuesAggregator.combineIntermediate(state, minNegX.getInt(0), minPosX.getInt(0), maxNegX.getInt(0), maxPosX.getInt(0), maxY.getInt(0), minY.getInt(0)); SpatialExtentGeoPointSourceValuesAggregator.combineIntermediate(state, top.getInt(0), bottom.getInt(0), negLeft.getInt(0), negRight.getInt(0), posLeft.getInt(0), posRight.getInt(0));
} }
@Override @Override

View file

@ -28,12 +28,12 @@ import org.elasticsearch.compute.operator.DriverContext;
*/ */
public final class SpatialExtentGeoPointSourceValuesGroupingAggregatorFunction implements GroupingAggregatorFunction { public final class SpatialExtentGeoPointSourceValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final SpatialExtentGroupingStateWrappedLongitudeState state; private final SpatialExtentGroupingStateWrappedLongitudeState state;
@ -173,40 +173,40 @@ public final class SpatialExtentGeoPointSourceValuesGroupingAggregatorFunction i
public void addIntermediateInput(int positionOffset, IntVector groups, Page page) { public void addIntermediateInput(int positionOffset, IntVector groups, Page page) {
state.enableGroupIdTracking(new SeenGroupIds.Empty()); state.enableGroupIdTracking(new SeenGroupIds.Empty());
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minNegX.getPositionCount() == minPosX.getPositionCount() && minNegX.getPositionCount() == maxNegX.getPositionCount() && minNegX.getPositionCount() == maxPosX.getPositionCount() && minNegX.getPositionCount() == maxY.getPositionCount() && minNegX.getPositionCount() == minY.getPositionCount(); assert top.getPositionCount() == bottom.getPositionCount() && top.getPositionCount() == negLeft.getPositionCount() && top.getPositionCount() == negRight.getPositionCount() && top.getPositionCount() == posLeft.getPositionCount() && top.getPositionCount() == posRight.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentGeoPointSourceValuesAggregator.combineIntermediate(state, groupId, minNegX.getInt(groupPosition + positionOffset), minPosX.getInt(groupPosition + positionOffset), maxNegX.getInt(groupPosition + positionOffset), maxPosX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset)); SpatialExtentGeoPointSourceValuesAggregator.combineIntermediate(state, groupId, top.getInt(groupPosition + positionOffset), bottom.getInt(groupPosition + positionOffset), negLeft.getInt(groupPosition + positionOffset), negRight.getInt(groupPosition + positionOffset), posLeft.getInt(groupPosition + positionOffset), posRight.getInt(groupPosition + positionOffset));
} }
} }

View file

@ -0,0 +1,196 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import org.elasticsearch.compute.aggregation.AggregatorFunction;
import org.elasticsearch.compute.aggregation.IntermediateStateDesc;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BooleanVector;
import org.elasticsearch.compute.data.ElementType;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.compute.data.IntVector;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link AggregatorFunction} implementation for {@link SpatialExtentGeoShapeDocValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentGeoShapeDocValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("posRight", ElementType.INT) );
private final DriverContext driverContext;
private final SpatialExtentStateWrappedLongitudeState state;
private final List<Integer> channels;
public SpatialExtentGeoShapeDocValuesAggregatorFunction(DriverContext driverContext,
List<Integer> channels, SpatialExtentStateWrappedLongitudeState state) {
this.driverContext = driverContext;
this.channels = channels;
this.state = state;
}
public static SpatialExtentGeoShapeDocValuesAggregatorFunction create(DriverContext driverContext,
List<Integer> channels) {
return new SpatialExtentGeoShapeDocValuesAggregatorFunction(driverContext, channels, SpatialExtentGeoShapeDocValuesAggregator.initSingle());
}
public static List<IntermediateStateDesc> intermediateStateDesc() {
return INTERMEDIATE_STATE_DESC;
}
@Override
public int intermediateBlockCount() {
return INTERMEDIATE_STATE_DESC.size();
}
@Override
public void addRawInput(Page page, BooleanVector mask) {
if (mask.allFalse()) {
// Entire page masked away
return;
}
if (mask.allTrue()) {
// No masking
IntBlock block = page.getBlock(channels.get(0));
IntVector vector = block.asVector();
if (vector != null) {
addRawVector(vector);
} else {
addRawBlock(block);
}
return;
}
// Some positions masked away, others kept
IntBlock block = page.getBlock(channels.get(0));
IntVector vector = block.asVector();
if (vector != null) {
addRawVector(vector, mask);
} else {
addRawBlock(block, mask);
}
}
private void addRawVector(IntVector vector) {
// This type does not support vectors because all values are multi-valued
}
private void addRawVector(IntVector vector, BooleanVector mask) {
// This type does not support vectors because all values are multi-valued
}
private void addRawBlock(IntBlock block) {
for (int p = 0; p < block.getPositionCount(); p++) {
if (block.isNull(p)) {
continue;
}
int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p);
int[] valuesArray = new int[end - start];
for (int i = start; i < end; i++) {
valuesArray[i-start] = block.getInt(i);
}
SpatialExtentGeoShapeDocValuesAggregator.combine(state, valuesArray);
}
}
private void addRawBlock(IntBlock block, BooleanVector mask) {
for (int p = 0; p < block.getPositionCount(); p++) {
if (mask.getBoolean(p) == false) {
continue;
}
if (block.isNull(p)) {
continue;
}
int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p);
int[] valuesArray = new int[end - start];
for (int i = start; i < end; i++) {
valuesArray[i-start] = block.getInt(i);
}
SpatialExtentGeoShapeDocValuesAggregator.combine(state, valuesArray);
}
}
@Override
public void addIntermediateInput(Page page) {
assert channels.size() == intermediateBlockCount();
assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size();
Block topUncast = page.getBlock(channels.get(0));
if (topUncast.areAllValuesNull()) {
return;
}
IntVector top = ((IntBlock) topUncast).asVector();
assert top.getPositionCount() == 1;
Block bottomUncast = page.getBlock(channels.get(1));
if (bottomUncast.areAllValuesNull()) {
return;
}
IntVector bottom = ((IntBlock) bottomUncast).asVector();
assert bottom.getPositionCount() == 1;
Block negLeftUncast = page.getBlock(channels.get(2));
if (negLeftUncast.areAllValuesNull()) {
return;
}
IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
assert negLeft.getPositionCount() == 1;
Block negRightUncast = page.getBlock(channels.get(3));
if (negRightUncast.areAllValuesNull()) {
return;
}
IntVector negRight = ((IntBlock) negRightUncast).asVector();
assert negRight.getPositionCount() == 1;
Block posLeftUncast = page.getBlock(channels.get(4));
if (posLeftUncast.areAllValuesNull()) {
return;
}
IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
assert posLeft.getPositionCount() == 1;
Block posRightUncast = page.getBlock(channels.get(5));
if (posRightUncast.areAllValuesNull()) {
return;
}
IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert posRight.getPositionCount() == 1;
SpatialExtentGeoShapeDocValuesAggregator.combineIntermediate(state, top.getInt(0), bottom.getInt(0), negLeft.getInt(0), negRight.getInt(0), posLeft.getInt(0), posRight.getInt(0));
}
@Override
public void evaluateIntermediate(Block[] blocks, int offset, DriverContext driverContext) {
state.toIntermediate(blocks, offset, driverContext);
}
@Override
public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) {
blocks[offset] = SpatialExtentGeoShapeDocValuesAggregator.evaluateFinal(state, driverContext);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append("[");
sb.append("channels=").append(channels);
sb.append("]");
return sb.toString();
}
@Override
public void close() {
state.close();
}
}

View file

@ -12,29 +12,29 @@ import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentGeoShapeAggregator}. * {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentGeoShapeDocValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentGeoShapeAggregatorFunctionSupplier implements AggregatorFunctionSupplier { public final class SpatialExtentGeoShapeDocValuesAggregatorFunctionSupplier implements AggregatorFunctionSupplier {
private final List<Integer> channels; private final List<Integer> channels;
public SpatialExtentGeoShapeAggregatorFunctionSupplier(List<Integer> channels) { public SpatialExtentGeoShapeDocValuesAggregatorFunctionSupplier(List<Integer> channels) {
this.channels = channels; this.channels = channels;
} }
@Override @Override
public SpatialExtentGeoShapeAggregatorFunction aggregator(DriverContext driverContext) { public SpatialExtentGeoShapeDocValuesAggregatorFunction aggregator(DriverContext driverContext) {
return SpatialExtentGeoShapeAggregatorFunction.create(driverContext, channels); return SpatialExtentGeoShapeDocValuesAggregatorFunction.create(driverContext, channels);
} }
@Override @Override
public SpatialExtentGeoShapeGroupingAggregatorFunction groupingAggregator( public SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction groupingAggregator(
DriverContext driverContext) { DriverContext driverContext) {
return SpatialExtentGeoShapeGroupingAggregatorFunction.create(channels, driverContext); return SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction.create(channels, driverContext);
} }
@Override @Override
public String describe() { public String describe() {
return "spatial_extent_geo of shapes"; return "spatial_extent_geo_shape_doc of valuess";
} }
} }

View file

@ -0,0 +1,231 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License
// 2.0; you may not use this file except in compliance with the Elastic License
// 2.0.
package org.elasticsearch.compute.aggregation.spatial;
import java.lang.Integer;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction;
import org.elasticsearch.compute.aggregation.IntermediateStateDesc;
import org.elasticsearch.compute.aggregation.SeenGroupIds;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.ElementType;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.compute.data.IntVector;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
/**
* {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentGeoShapeDocValuesAggregator}.
* This class is generated. Do not edit it.
*/
public final class SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("posRight", ElementType.INT) );
private final SpatialExtentGroupingStateWrappedLongitudeState state;
private final List<Integer> channels;
private final DriverContext driverContext;
public SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction(List<Integer> channels,
SpatialExtentGroupingStateWrappedLongitudeState state, DriverContext driverContext) {
this.channels = channels;
this.state = state;
this.driverContext = driverContext;
}
public static SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction create(
List<Integer> channels, DriverContext driverContext) {
return new SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction(channels, SpatialExtentGeoShapeDocValuesAggregator.initGrouping(), driverContext);
}
public static List<IntermediateStateDesc> intermediateStateDesc() {
return INTERMEDIATE_STATE_DESC;
}
@Override
public int intermediateBlockCount() {
return INTERMEDIATE_STATE_DESC.size();
}
@Override
public GroupingAggregatorFunction.AddInput prepareProcessPage(SeenGroupIds seenGroupIds,
Page page) {
IntBlock valuesBlock = page.getBlock(channels.get(0));
IntVector valuesVector = valuesBlock.asVector();
if (valuesVector == null) {
if (valuesBlock.mayHaveNulls()) {
state.enableGroupIdTracking(seenGroupIds);
}
return new GroupingAggregatorFunction.AddInput() {
@Override
public void add(int positionOffset, IntBlock groupIds) {
addRawInput(positionOffset, groupIds, valuesBlock);
}
@Override
public void add(int positionOffset, IntVector groupIds) {
addRawInput(positionOffset, groupIds, valuesBlock);
}
@Override
public void close() {
}
};
}
return new GroupingAggregatorFunction.AddInput() {
@Override
public void add(int positionOffset, IntBlock groupIds) {
addRawInput(positionOffset, groupIds, valuesVector);
}
@Override
public void add(int positionOffset, IntVector groupIds) {
addRawInput(positionOffset, groupIds, valuesVector);
}
@Override
public void close() {
}
};
}
private void addRawInput(int positionOffset, IntVector groups, IntBlock values) {
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition);
if (values.isNull(groupPosition + positionOffset)) {
continue;
}
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
int[] valuesArray = new int[valuesEnd - valuesStart];
for (int v = valuesStart; v < valuesEnd; v++) {
valuesArray[v-valuesStart] = values.getInt(v);
}
SpatialExtentGeoShapeDocValuesAggregator.combine(state, groupId, valuesArray);
}
}
private void addRawInput(int positionOffset, IntVector groups, IntVector values) {
// This type does not support vectors because all values are multi-valued
}
private void addRawInput(int positionOffset, IntBlock groups, IntBlock values) {
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
if (groups.isNull(groupPosition)) {
continue;
}
int groupStart = groups.getFirstValueIndex(groupPosition);
int groupEnd = groupStart + groups.getValueCount(groupPosition);
for (int g = groupStart; g < groupEnd; g++) {
int groupId = groups.getInt(g);
if (values.isNull(groupPosition + positionOffset)) {
continue;
}
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
int[] valuesArray = new int[valuesEnd - valuesStart];
for (int v = valuesStart; v < valuesEnd; v++) {
valuesArray[v-valuesStart] = values.getInt(v);
}
SpatialExtentGeoShapeDocValuesAggregator.combine(state, groupId, valuesArray);
}
}
}
private void addRawInput(int positionOffset, IntBlock groups, IntVector values) {
// This type does not support vectors because all values are multi-valued
}
@Override
public void selectedMayContainUnseenGroups(SeenGroupIds seenGroupIds) {
state.enableGroupIdTracking(seenGroupIds);
}
@Override
public void addIntermediateInput(int positionOffset, IntVector groups, Page page) {
state.enableGroupIdTracking(new SeenGroupIds.Empty());
assert channels.size() == intermediateBlockCount();
Block topUncast = page.getBlock(channels.get(0));
if (topUncast.areAllValuesNull()) {
return;
}
IntVector top = ((IntBlock) topUncast).asVector();
Block bottomUncast = page.getBlock(channels.get(1));
if (bottomUncast.areAllValuesNull()) {
return;
}
IntVector bottom = ((IntBlock) bottomUncast).asVector();
Block negLeftUncast = page.getBlock(channels.get(2));
if (negLeftUncast.areAllValuesNull()) {
return;
}
IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
Block negRightUncast = page.getBlock(channels.get(3));
if (negRightUncast.areAllValuesNull()) {
return;
}
IntVector negRight = ((IntBlock) negRightUncast).asVector();
Block posLeftUncast = page.getBlock(channels.get(4));
if (posLeftUncast.areAllValuesNull()) {
return;
}
IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
Block posRightUncast = page.getBlock(channels.get(5));
if (posRightUncast.areAllValuesNull()) {
return;
}
IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert top.getPositionCount() == bottom.getPositionCount() && top.getPositionCount() == negLeft.getPositionCount() && top.getPositionCount() == negRight.getPositionCount() && top.getPositionCount() == posLeft.getPositionCount() && top.getPositionCount() == posRight.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition);
SpatialExtentGeoShapeDocValuesAggregator.combineIntermediate(state, groupId, top.getInt(groupPosition + positionOffset), bottom.getInt(groupPosition + positionOffset), negLeft.getInt(groupPosition + positionOffset), negRight.getInt(groupPosition + positionOffset), posLeft.getInt(groupPosition + positionOffset), posRight.getInt(groupPosition + positionOffset));
}
}
@Override
public void addIntermediateRowInput(int groupId, GroupingAggregatorFunction input, int position) {
if (input.getClass() != getClass()) {
throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass());
}
SpatialExtentGroupingStateWrappedLongitudeState inState = ((SpatialExtentGeoShapeDocValuesGroupingAggregatorFunction) input).state;
state.enableGroupIdTracking(new SeenGroupIds.Empty());
SpatialExtentGeoShapeDocValuesAggregator.combineStates(state, groupId, inState, position);
}
@Override
public void evaluateIntermediate(Block[] blocks, int offset, IntVector selected) {
state.toIntermediate(blocks, offset, selected, driverContext);
}
@Override
public void evaluateFinal(Block[] blocks, int offset, IntVector selected,
DriverContext driverContext) {
blocks[offset] = SpatialExtentGeoShapeDocValuesAggregator.evaluateFinal(state, selected, driverContext);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append("[");
sb.append("channels=").append(channels);
sb.append("]");
return sb.toString();
}
@Override
public void close() {
state.close();
}
}

View file

@ -23,17 +23,17 @@ import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link AggregatorFunction} implementation for {@link SpatialExtentGeoShapeAggregator}. * {@link AggregatorFunction} implementation for {@link SpatialExtentGeoShapeSourceValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentGeoShapeAggregatorFunction implements AggregatorFunction { public final class SpatialExtentGeoShapeSourceValuesAggregatorFunction implements AggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final DriverContext driverContext; private final DriverContext driverContext;
@ -41,16 +41,16 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
private final List<Integer> channels; private final List<Integer> channels;
public SpatialExtentGeoShapeAggregatorFunction(DriverContext driverContext, public SpatialExtentGeoShapeSourceValuesAggregatorFunction(DriverContext driverContext,
List<Integer> channels, SpatialExtentStateWrappedLongitudeState state) { List<Integer> channels, SpatialExtentStateWrappedLongitudeState state) {
this.driverContext = driverContext; this.driverContext = driverContext;
this.channels = channels; this.channels = channels;
this.state = state; this.state = state;
} }
public static SpatialExtentGeoShapeAggregatorFunction create(DriverContext driverContext, public static SpatialExtentGeoShapeSourceValuesAggregatorFunction create(
List<Integer> channels) { DriverContext driverContext, List<Integer> channels) {
return new SpatialExtentGeoShapeAggregatorFunction(driverContext, channels, SpatialExtentGeoShapeAggregator.initSingle()); return new SpatialExtentGeoShapeSourceValuesAggregatorFunction(driverContext, channels, SpatialExtentGeoShapeSourceValuesAggregator.initSingle());
} }
public static List<IntermediateStateDesc> intermediateStateDesc() { public static List<IntermediateStateDesc> intermediateStateDesc() {
@ -92,7 +92,7 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
private void addRawVector(BytesRefVector vector) { private void addRawVector(BytesRefVector vector) {
BytesRef scratch = new BytesRef(); BytesRef scratch = new BytesRef();
for (int i = 0; i < vector.getPositionCount(); i++) { for (int i = 0; i < vector.getPositionCount(); i++) {
SpatialExtentGeoShapeAggregator.combine(state, vector.getBytesRef(i, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, vector.getBytesRef(i, scratch));
} }
} }
@ -102,7 +102,7 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
if (mask.getBoolean(i) == false) { if (mask.getBoolean(i) == false) {
continue; continue;
} }
SpatialExtentGeoShapeAggregator.combine(state, vector.getBytesRef(i, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, vector.getBytesRef(i, scratch));
} }
} }
@ -115,7 +115,7 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
int start = block.getFirstValueIndex(p); int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p); int end = start + block.getValueCount(p);
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
SpatialExtentGeoShapeAggregator.combine(state, block.getBytesRef(i, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, block.getBytesRef(i, scratch));
} }
} }
} }
@ -132,7 +132,7 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
int start = block.getFirstValueIndex(p); int start = block.getFirstValueIndex(p);
int end = start + block.getValueCount(p); int end = start + block.getValueCount(p);
for (int i = start; i < end; i++) { for (int i = start; i < end; i++) {
SpatialExtentGeoShapeAggregator.combine(state, block.getBytesRef(i, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, block.getBytesRef(i, scratch));
} }
} }
} }
@ -141,43 +141,43 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
public void addIntermediateInput(Page page) { public void addIntermediateInput(Page page) {
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size(); assert page.getBlockCount() >= channels.get(0) + intermediateStateDesc().size();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
assert minNegX.getPositionCount() == 1; assert top.getPositionCount() == 1;
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
assert minPosX.getPositionCount() == 1; assert bottom.getPositionCount() == 1;
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
assert maxNegX.getPositionCount() == 1; assert negLeft.getPositionCount() == 1;
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
assert maxPosX.getPositionCount() == 1; assert negRight.getPositionCount() == 1;
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
assert maxY.getPositionCount() == 1; assert posLeft.getPositionCount() == 1;
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minY.getPositionCount() == 1; assert posRight.getPositionCount() == 1;
SpatialExtentGeoShapeAggregator.combineIntermediate(state, minNegX.getInt(0), minPosX.getInt(0), maxNegX.getInt(0), maxPosX.getInt(0), maxY.getInt(0), minY.getInt(0)); SpatialExtentGeoShapeSourceValuesAggregator.combineIntermediate(state, top.getInt(0), bottom.getInt(0), negLeft.getInt(0), negRight.getInt(0), posLeft.getInt(0), posRight.getInt(0));
} }
@Override @Override
@ -187,7 +187,7 @@ public final class SpatialExtentGeoShapeAggregatorFunction implements Aggregator
@Override @Override
public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) { public void evaluateFinal(Block[] blocks, int offset, DriverContext driverContext) {
blocks[offset] = SpatialExtentGeoShapeAggregator.evaluateFinal(state, driverContext); blocks[offset] = SpatialExtentGeoShapeSourceValuesAggregator.evaluateFinal(state, driverContext);
} }
@Override @Override

View file

@ -12,29 +12,30 @@ import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentCartesianShapeAggregator}. * {@link AggregatorFunctionSupplier} implementation for {@link SpatialExtentGeoShapeSourceValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentCartesianShapeAggregatorFunctionSupplier implements AggregatorFunctionSupplier { public final class SpatialExtentGeoShapeSourceValuesAggregatorFunctionSupplier implements AggregatorFunctionSupplier {
private final List<Integer> channels; private final List<Integer> channels;
public SpatialExtentCartesianShapeAggregatorFunctionSupplier(List<Integer> channels) { public SpatialExtentGeoShapeSourceValuesAggregatorFunctionSupplier(List<Integer> channels) {
this.channels = channels; this.channels = channels;
} }
@Override @Override
public SpatialExtentCartesianShapeAggregatorFunction aggregator(DriverContext driverContext) { public SpatialExtentGeoShapeSourceValuesAggregatorFunction aggregator(
return SpatialExtentCartesianShapeAggregatorFunction.create(driverContext, channels); DriverContext driverContext) {
return SpatialExtentGeoShapeSourceValuesAggregatorFunction.create(driverContext, channels);
} }
@Override @Override
public SpatialExtentCartesianShapeGroupingAggregatorFunction groupingAggregator( public SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction groupingAggregator(
DriverContext driverContext) { DriverContext driverContext) {
return SpatialExtentCartesianShapeGroupingAggregatorFunction.create(channels, driverContext); return SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction.create(channels, driverContext);
} }
@Override @Override
public String describe() { public String describe() {
return "spatial_extent_cartesian of shapes"; return "spatial_extent_geo_shape_source of valuess";
} }
} }

View file

@ -23,17 +23,17 @@ import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
/** /**
* {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentGeoShapeAggregator}. * {@link GroupingAggregatorFunction} implementation for {@link SpatialExtentGeoShapeSourceValuesAggregator}.
* This class is generated. Do not edit it. * This class is generated. Do not edit it.
*/ */
public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements GroupingAggregatorFunction { public final class SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction implements GroupingAggregatorFunction {
private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of( private static final List<IntermediateStateDesc> INTERMEDIATE_STATE_DESC = List.of(
new IntermediateStateDesc("minNegX", ElementType.INT), new IntermediateStateDesc("top", ElementType.INT),
new IntermediateStateDesc("minPosX", ElementType.INT), new IntermediateStateDesc("bottom", ElementType.INT),
new IntermediateStateDesc("maxNegX", ElementType.INT), new IntermediateStateDesc("negLeft", ElementType.INT),
new IntermediateStateDesc("maxPosX", ElementType.INT), new IntermediateStateDesc("negRight", ElementType.INT),
new IntermediateStateDesc("maxY", ElementType.INT), new IntermediateStateDesc("posLeft", ElementType.INT),
new IntermediateStateDesc("minY", ElementType.INT) ); new IntermediateStateDesc("posRight", ElementType.INT) );
private final SpatialExtentGroupingStateWrappedLongitudeState state; private final SpatialExtentGroupingStateWrappedLongitudeState state;
@ -41,16 +41,16 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
private final DriverContext driverContext; private final DriverContext driverContext;
public SpatialExtentGeoShapeGroupingAggregatorFunction(List<Integer> channels, public SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction(List<Integer> channels,
SpatialExtentGroupingStateWrappedLongitudeState state, DriverContext driverContext) { SpatialExtentGroupingStateWrappedLongitudeState state, DriverContext driverContext) {
this.channels = channels; this.channels = channels;
this.state = state; this.state = state;
this.driverContext = driverContext; this.driverContext = driverContext;
} }
public static SpatialExtentGeoShapeGroupingAggregatorFunction create(List<Integer> channels, public static SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction create(
DriverContext driverContext) { List<Integer> channels, DriverContext driverContext) {
return new SpatialExtentGeoShapeGroupingAggregatorFunction(channels, SpatialExtentGeoShapeAggregator.initGrouping(), driverContext); return new SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction(channels, SpatialExtentGeoShapeSourceValuesAggregator.initGrouping(), driverContext);
} }
public static List<IntermediateStateDesc> intermediateStateDesc() { public static List<IntermediateStateDesc> intermediateStateDesc() {
@ -114,7 +114,7 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset); int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset); int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
for (int v = valuesStart; v < valuesEnd; v++) { for (int v = valuesStart; v < valuesEnd; v++) {
SpatialExtentGeoShapeAggregator.combine(state, groupId, values.getBytesRef(v, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(v, scratch));
} }
} }
} }
@ -123,7 +123,7 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
BytesRef scratch = new BytesRef(); BytesRef scratch = new BytesRef();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentGeoShapeAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch));
} }
} }
@ -143,7 +143,7 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset); int valuesStart = values.getFirstValueIndex(groupPosition + positionOffset);
int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset); int valuesEnd = valuesStart + values.getValueCount(groupPosition + positionOffset);
for (int v = valuesStart; v < valuesEnd; v++) { for (int v = valuesStart; v < valuesEnd; v++) {
SpatialExtentGeoShapeAggregator.combine(state, groupId, values.getBytesRef(v, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(v, scratch));
} }
} }
} }
@ -159,7 +159,7 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
int groupEnd = groupStart + groups.getValueCount(groupPosition); int groupEnd = groupStart + groups.getValueCount(groupPosition);
for (int g = groupStart; g < groupEnd; g++) { for (int g = groupStart; g < groupEnd; g++) {
int groupId = groups.getInt(g); int groupId = groups.getInt(g);
SpatialExtentGeoShapeAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch)); SpatialExtentGeoShapeSourceValuesAggregator.combine(state, groupId, values.getBytesRef(groupPosition + positionOffset, scratch));
} }
} }
} }
@ -173,40 +173,40 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
public void addIntermediateInput(int positionOffset, IntVector groups, Page page) { public void addIntermediateInput(int positionOffset, IntVector groups, Page page) {
state.enableGroupIdTracking(new SeenGroupIds.Empty()); state.enableGroupIdTracking(new SeenGroupIds.Empty());
assert channels.size() == intermediateBlockCount(); assert channels.size() == intermediateBlockCount();
Block minNegXUncast = page.getBlock(channels.get(0)); Block topUncast = page.getBlock(channels.get(0));
if (minNegXUncast.areAllValuesNull()) { if (topUncast.areAllValuesNull()) {
return; return;
} }
IntVector minNegX = ((IntBlock) minNegXUncast).asVector(); IntVector top = ((IntBlock) topUncast).asVector();
Block minPosXUncast = page.getBlock(channels.get(1)); Block bottomUncast = page.getBlock(channels.get(1));
if (minPosXUncast.areAllValuesNull()) { if (bottomUncast.areAllValuesNull()) {
return; return;
} }
IntVector minPosX = ((IntBlock) minPosXUncast).asVector(); IntVector bottom = ((IntBlock) bottomUncast).asVector();
Block maxNegXUncast = page.getBlock(channels.get(2)); Block negLeftUncast = page.getBlock(channels.get(2));
if (maxNegXUncast.areAllValuesNull()) { if (negLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxNegX = ((IntBlock) maxNegXUncast).asVector(); IntVector negLeft = ((IntBlock) negLeftUncast).asVector();
Block maxPosXUncast = page.getBlock(channels.get(3)); Block negRightUncast = page.getBlock(channels.get(3));
if (maxPosXUncast.areAllValuesNull()) { if (negRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxPosX = ((IntBlock) maxPosXUncast).asVector(); IntVector negRight = ((IntBlock) negRightUncast).asVector();
Block maxYUncast = page.getBlock(channels.get(4)); Block posLeftUncast = page.getBlock(channels.get(4));
if (maxYUncast.areAllValuesNull()) { if (posLeftUncast.areAllValuesNull()) {
return; return;
} }
IntVector maxY = ((IntBlock) maxYUncast).asVector(); IntVector posLeft = ((IntBlock) posLeftUncast).asVector();
Block minYUncast = page.getBlock(channels.get(5)); Block posRightUncast = page.getBlock(channels.get(5));
if (minYUncast.areAllValuesNull()) { if (posRightUncast.areAllValuesNull()) {
return; return;
} }
IntVector minY = ((IntBlock) minYUncast).asVector(); IntVector posRight = ((IntBlock) posRightUncast).asVector();
assert minNegX.getPositionCount() == minPosX.getPositionCount() && minNegX.getPositionCount() == maxNegX.getPositionCount() && minNegX.getPositionCount() == maxPosX.getPositionCount() && minNegX.getPositionCount() == maxY.getPositionCount() && minNegX.getPositionCount() == minY.getPositionCount(); assert top.getPositionCount() == bottom.getPositionCount() && top.getPositionCount() == negLeft.getPositionCount() && top.getPositionCount() == negRight.getPositionCount() && top.getPositionCount() == posLeft.getPositionCount() && top.getPositionCount() == posRight.getPositionCount();
for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) { for (int groupPosition = 0; groupPosition < groups.getPositionCount(); groupPosition++) {
int groupId = groups.getInt(groupPosition); int groupId = groups.getInt(groupPosition);
SpatialExtentGeoShapeAggregator.combineIntermediate(state, groupId, minNegX.getInt(groupPosition + positionOffset), minPosX.getInt(groupPosition + positionOffset), maxNegX.getInt(groupPosition + positionOffset), maxPosX.getInt(groupPosition + positionOffset), maxY.getInt(groupPosition + positionOffset), minY.getInt(groupPosition + positionOffset)); SpatialExtentGeoShapeSourceValuesAggregator.combineIntermediate(state, groupId, top.getInt(groupPosition + positionOffset), bottom.getInt(groupPosition + positionOffset), negLeft.getInt(groupPosition + positionOffset), negRight.getInt(groupPosition + positionOffset), posLeft.getInt(groupPosition + positionOffset), posRight.getInt(groupPosition + positionOffset));
} }
} }
@ -215,9 +215,9 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
if (input.getClass() != getClass()) { if (input.getClass() != getClass()) {
throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass()); throw new IllegalArgumentException("expected " + getClass() + "; got " + input.getClass());
} }
SpatialExtentGroupingStateWrappedLongitudeState inState = ((SpatialExtentGeoShapeGroupingAggregatorFunction) input).state; SpatialExtentGroupingStateWrappedLongitudeState inState = ((SpatialExtentGeoShapeSourceValuesGroupingAggregatorFunction) input).state;
state.enableGroupIdTracking(new SeenGroupIds.Empty()); state.enableGroupIdTracking(new SeenGroupIds.Empty());
SpatialExtentGeoShapeAggregator.combineStates(state, groupId, inState, position); SpatialExtentGeoShapeSourceValuesAggregator.combineStates(state, groupId, inState, position);
} }
@Override @Override
@ -228,7 +228,7 @@ public final class SpatialExtentGeoShapeGroupingAggregatorFunction implements Gr
@Override @Override
public void evaluateFinal(Block[] blocks, int offset, IntVector selected, public void evaluateFinal(Block[] blocks, int offset, IntVector selected,
DriverContext driverContext) { DriverContext driverContext) {
blocks[offset] = SpatialExtentGeoShapeAggregator.evaluateFinal(state, selected, driverContext); blocks[offset] = SpatialExtentGeoShapeSourceValuesAggregator.evaluateFinal(state, selected, driverContext);
} }
@Override @Override

View file

@ -1,63 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.compute.aggregation.spatial;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor.WrapLongitude;
class GeoPointEnvelopeVisitor extends SpatialEnvelopeVisitor.GeoPointVisitor {
GeoPointEnvelopeVisitor() {
super(WrapLongitude.WRAP);
}
void reset() {
minY = Double.POSITIVE_INFINITY;
maxY = Double.NEGATIVE_INFINITY;
minNegX = Double.POSITIVE_INFINITY;
maxNegX = Double.NEGATIVE_INFINITY;
minPosX = Double.POSITIVE_INFINITY;
maxPosX = Double.NEGATIVE_INFINITY;
}
double getMinNegX() {
return minNegX;
}
double getMinPosX() {
return minPosX;
}
double getMaxNegX() {
return maxNegX;
}
double getMaxPosX() {
return maxPosX;
}
double getMaxY() {
return maxY;
}
double getMinY() {
return minY;
}
static Rectangle asRectangle(
double minNegX,
double minPosX,
double maxNegX,
double maxPosX,
double maxY,
double minY,
WrapLongitude wrapLongitude
) {
return SpatialEnvelopeVisitor.GeoPointVisitor.getResult(minNegX, minPosX, maxNegX, maxPosX, maxY, minY, wrapLongitude);
}
}

View file

@ -12,12 +12,10 @@ import org.apache.lucene.geo.XYEncodingUtils;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Point; import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.GeometryValidator; import org.elasticsearch.geometry.utils.GeometryValidator;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor.WrapLongitude;
import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownBinary;
class SpatialAggregationUtils { public class SpatialAggregationUtils {
private SpatialAggregationUtils() { /* Utility class */ } private SpatialAggregationUtils() { /* Utility class */ }
public static Geometry decode(BytesRef wkb) { public static Geometry decode(BytesRef wkb) {
@ -52,26 +50,12 @@ class SpatialAggregationUtils {
return GeoEncodingUtils.decodeLatitude((int) (encoded >>> 32)); return GeoEncodingUtils.decodeLatitude((int) (encoded >>> 32));
} }
public static int encodeNegativeLongitude(double d) { public static int encodeLongitude(double d) {
return Double.isFinite(d) ? GeoEncodingUtils.encodeLongitude(d) : DEFAULT_NEG; return Double.isFinite(d) ? GeoEncodingUtils.encodeLongitude(d) : encodeInfinity(d);
} }
public static int encodePositiveLongitude(double d) { private static int encodeInfinity(double d) {
return Double.isFinite(d) ? GeoEncodingUtils.encodeLongitude(d) : DEFAULT_POS; return d == Double.NEGATIVE_INFINITY ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
public static Rectangle asRectangle(int minNegX, int minPosX, int maxNegX, int maxPosX, int maxY, int minY) {
assert minNegX <= 0 == maxNegX <= 0;
assert minPosX >= 0 == maxPosX >= 0;
return GeoPointEnvelopeVisitor.asRectangle(
minNegX <= 0 ? decodeLongitude(minNegX) : Double.POSITIVE_INFINITY,
minPosX >= 0 ? decodeLongitude(minPosX) : Double.POSITIVE_INFINITY,
maxNegX <= 0 ? decodeLongitude(maxNegX) : Double.NEGATIVE_INFINITY,
maxPosX >= 0 ? decodeLongitude(maxPosX) : Double.NEGATIVE_INFINITY,
GeoEncodingUtils.decodeLatitude(maxY),
GeoEncodingUtils.decodeLatitude(minY),
WrapLongitude.WRAP
);
} }
public static int maxNeg(int a, int b) { public static int maxNeg(int a, int b) {
@ -81,8 +65,4 @@ class SpatialAggregationUtils {
public static int minPos(int a, int b) { public static int minPos(int a, int b) {
return a >= 0 && b >= 0 ? Math.min(a, b) : Math.max(a, b); return a >= 0 && b >= 0 ? Math.min(a, b) : Math.max(a, b);
} }
// The default values are intentionally non-negative/non-positive, so we can mark unassigned values.
public static final int DEFAULT_POS = -1;
public static final int DEFAULT_NEG = 1;
} }

View file

@ -14,6 +14,9 @@ import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of cartesian points. It is assumed the points are encoded as longs. * Computes the extent of a set of cartesian points. It is assumed the points are encoded as longs.
* This requires that the planner has planned that points are loaded from the index as doc-values. * This requires that the planner has planned that points are loaded from the index as doc-values.
* The intermediate state is the extent of the shapes, encoded as four integers: minX, maxX, maxY, minY.
* The order of the integers is the same as defined in the constructor of the Rectangle class.
* Note that this is very different from the six values used for the intermediate state of geo_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {

View file

@ -15,8 +15,9 @@ import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of cartesian points. It is assumed that the cartesian points are encoded as WKB BytesRef. * Computes the extent of a set of cartesian points. It is assumed that the cartesian points are encoded as WKB BytesRef.
* This requires that the planner has NOT planned that points are loaded from the index as doc-values, but from source instead. * This requires that the planner has NOT planned that points are loaded from the index as doc-values, but from source instead.
* This is also used for final aggregations and aggregations in the coordinator node, * The intermediate state is the extent of the shapes, encoded as four integers: minX, maxX, maxY, minY.
* even if the local node partial aggregation is done with {@link SpatialExtentCartesianPointDocValuesAggregator}. * The order of the integers is the same as defined in the constructor of the Rectangle class.
* Note that this is very different from the six values used for the intermediate state of geo_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.compute.aggregation.spatial;
import org.elasticsearch.compute.ann.Aggregator;
import org.elasticsearch.compute.ann.GroupingAggregator;
import org.elasticsearch.compute.ann.IntermediateState;
/**
* Computes the extent of a set of cartesian shapes read from doc-values, which means they are encoded as an array of integers.
* This requires that the planner has planned that the shape extent is loaded from the index as doc-values.
* The intermediate state is the extent of the shapes, encoded as four integers: minX, maxX, maxY, minY.
* The order of the integers is the same as defined in the constructor of the Rectangle class.
* Note that this is very different from the six values used for the intermediate state of geo_shape geometries.
*/
@Aggregator(
{
@IntermediateState(name = "minX", type = "INT"),
@IntermediateState(name = "maxX", type = "INT"),
@IntermediateState(name = "maxY", type = "INT"),
@IntermediateState(name = "minY", type = "INT") }
)
@GroupingAggregator
class SpatialExtentCartesianShapeDocValuesAggregator extends SpatialExtentAggregator {
public static SpatialExtentState initSingle() {
return new SpatialExtentState(PointType.CARTESIAN);
}
public static SpatialExtentGroupingState initGrouping() {
return new SpatialExtentGroupingState(PointType.CARTESIAN);
}
public static void combine(SpatialExtentState current, int[] values) {
current.add(values);
}
public static void combine(SpatialExtentGroupingState current, int groupId, int[] values) {
current.add(groupId, values);
}
}

View file

@ -13,8 +13,11 @@ import org.elasticsearch.compute.ann.GroupingAggregator;
import org.elasticsearch.compute.ann.IntermediateState; import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of cartesian shapes. It is assumed that the cartesian shapes are encoded as WKB BytesRef. * Computes the extent of a set of cartesian shapes read from source, which means they are encoded as WKB BytesRef.
* We do not currently support reading shape values or extents from doc values. * This requires that the planner has NOT planned that shapes are loaded from the index as doc-values, but from source instead.
* The intermediate state is the extent of the shapes, encoded as four integers: minX, maxX, maxY, minY.
* The order of the integers is the same as defined in the constructor of the Rectangle class.
* Note that this is very different from the six values used for the intermediate state of geo_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {
@ -24,7 +27,7 @@ import org.elasticsearch.compute.ann.IntermediateState;
@IntermediateState(name = "minY", type = "INT") } @IntermediateState(name = "minY", type = "INT") }
) )
@GroupingAggregator @GroupingAggregator
class SpatialExtentCartesianShapeAggregator extends SpatialExtentAggregator { class SpatialExtentCartesianShapeSourceValuesAggregator extends SpatialExtentAggregator {
public static SpatialExtentState initSingle() { public static SpatialExtentState initSingle() {
return new SpatialExtentState(PointType.CARTESIAN); return new SpatialExtentState(PointType.CARTESIAN);
} }

View file

@ -14,15 +14,19 @@ import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of geo points. It is assumed the points are encoded as longs. * Computes the extent of a set of geo points. It is assumed the points are encoded as longs.
* This requires that the planner has planned that points are loaded from the index as doc-values. * This requires that the planner has planned that points are loaded from the index as doc-values.
* The intermediate state is the extent of the shapes, encoded as six integers: top, bottom, negLeft, negRight, posLeft, posRight.
* The order of the integers is the same as defined in the constructor of the Extent class,
* as that is the order in which the values are stored in shape doc-values.
* Note that this is very different from the four values used for the intermediate state of cartesian_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {
@IntermediateState(name = "minNegX", type = "INT"), @IntermediateState(name = "top", type = "INT"),
@IntermediateState(name = "minPosX", type = "INT"), @IntermediateState(name = "bottom", type = "INT"),
@IntermediateState(name = "maxNegX", type = "INT"), @IntermediateState(name = "negLeft", type = "INT"),
@IntermediateState(name = "maxPosX", type = "INT"), @IntermediateState(name = "negRight", type = "INT"),
@IntermediateState(name = "maxY", type = "INT"), @IntermediateState(name = "posLeft", type = "INT"),
@IntermediateState(name = "minY", type = "INT") } @IntermediateState(name = "posRight", type = "INT") }
) )
@GroupingAggregator @GroupingAggregator
class SpatialExtentGeoPointDocValuesAggregator extends SpatialExtentLongitudeWrappingAggregator { class SpatialExtentGeoPointDocValuesAggregator extends SpatialExtentLongitudeWrappingAggregator {

View file

@ -15,17 +15,19 @@ import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of geo points. It is assumed that the geo points are encoded as WKB BytesRef. * Computes the extent of a set of geo points. It is assumed that the geo points are encoded as WKB BytesRef.
* This requires that the planner has NOT planned that points are loaded from the index as doc-values, but from source instead. * This requires that the planner has NOT planned that points are loaded from the index as doc-values, but from source instead.
* This is also used for final aggregations and aggregations in the coordinator node, * The intermediate state is the extent of the shapes, encoded as six integers: top, bottom, negLeft, negRight, posLeft, posRight.
* even if the local node partial aggregation is done with {@link SpatialExtentGeoPointDocValuesAggregator}. * The order of the integers is the same as defined in the constructor of the Extent class,
* as that is the order in which the values are stored in shape doc-values.
* Note that this is very different from the four values used for the intermediate state of cartesian_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {
@IntermediateState(name = "minNegX", type = "INT"), @IntermediateState(name = "top", type = "INT"),
@IntermediateState(name = "minPosX", type = "INT"), @IntermediateState(name = "bottom", type = "INT"),
@IntermediateState(name = "maxNegX", type = "INT"), @IntermediateState(name = "negLeft", type = "INT"),
@IntermediateState(name = "maxPosX", type = "INT"), @IntermediateState(name = "negRight", type = "INT"),
@IntermediateState(name = "maxY", type = "INT"), @IntermediateState(name = "posLeft", type = "INT"),
@IntermediateState(name = "minY", type = "INT") } @IntermediateState(name = "posRight", type = "INT") }
) )
@GroupingAggregator @GroupingAggregator
class SpatialExtentGeoPointSourceValuesAggregator extends SpatialExtentLongitudeWrappingAggregator { class SpatialExtentGeoPointSourceValuesAggregator extends SpatialExtentLongitudeWrappingAggregator {

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.compute.aggregation.spatial;
import org.elasticsearch.compute.ann.Aggregator;
import org.elasticsearch.compute.ann.GroupingAggregator;
import org.elasticsearch.compute.ann.IntermediateState;
/**
* Computes the extent of a set of geo shapes read from doc-values, which means they are encoded as an array of integers.
* This requires that the planner has planned that the shape extent is loaded from the index as doc-values.
* The intermediate state is the extent of the shapes, encoded as six integers: top, bottom, negLeft, negRight, posLeft, posRight.
* The order of the integers is the same as defined in the constructor of the Extent class,
* as that is the order in which the values are stored in shape doc-values.
* Note that this is very different from the four values used for the intermediate state of cartesian_shape geometries.
*/
@Aggregator(
{
@IntermediateState(name = "top", type = "INT"),
@IntermediateState(name = "bottom", type = "INT"),
@IntermediateState(name = "negLeft", type = "INT"),
@IntermediateState(name = "negRight", type = "INT"),
@IntermediateState(name = "posLeft", type = "INT"),
@IntermediateState(name = "posRight", type = "INT") }
)
@GroupingAggregator
class SpatialExtentGeoShapeDocValuesAggregator extends SpatialExtentLongitudeWrappingAggregator {
public static SpatialExtentStateWrappedLongitudeState initSingle() {
return new SpatialExtentStateWrappedLongitudeState();
}
public static SpatialExtentGroupingStateWrappedLongitudeState initGrouping() {
return new SpatialExtentGroupingStateWrappedLongitudeState();
}
public static void combine(SpatialExtentStateWrappedLongitudeState current, int[] values) {
current.add(values);
}
public static void combine(SpatialExtentGroupingStateWrappedLongitudeState current, int groupId, int[] values) {
current.add(groupId, values);
}
}

View file

@ -13,21 +13,24 @@ import org.elasticsearch.compute.ann.GroupingAggregator;
import org.elasticsearch.compute.ann.IntermediateState; import org.elasticsearch.compute.ann.IntermediateState;
/** /**
* Computes the extent of a set of geo shapes. It is assumed that the geo shapes are encoded as WKB BytesRef. * Computes the extent of a set of geo shapes read from source, which means they are encoded as WKB BytesRef.
* We do not currently support reading shape values or extents from doc values. * This requires that the planner has NOT planned that shapes are loaded from the index as doc-values, but from source instead.
* The intermediate state is the extent of the shapes, encoded as six integers: top, bottom, negLeft, negRight, posLeft, posRight.
* The order of the integers is the same as defined in the constructor of the Extent class,
* as that is the order in which the values are stored in shape doc-values.
* Note that this is very different from the four values used for the intermediate state of cartesian_shape geometries.
*/ */
@Aggregator( @Aggregator(
{ {
@IntermediateState(name = "minNegX", type = "INT"), @IntermediateState(name = "top", type = "INT"),
@IntermediateState(name = "minPosX", type = "INT"), @IntermediateState(name = "bottom", type = "INT"),
@IntermediateState(name = "maxNegX", type = "INT"), @IntermediateState(name = "negLeft", type = "INT"),
@IntermediateState(name = "maxPosX", type = "INT"), @IntermediateState(name = "negRight", type = "INT"),
@IntermediateState(name = "maxY", type = "INT"), @IntermediateState(name = "posLeft", type = "INT"),
@IntermediateState(name = "minY", type = "INT") } @IntermediateState(name = "posRight", type = "INT") }
) )
@GroupingAggregator @GroupingAggregator
class SpatialExtentGeoShapeAggregator extends SpatialExtentLongitudeWrappingAggregator { class SpatialExtentGeoShapeSourceValuesAggregator extends SpatialExtentLongitudeWrappingAggregator {
// TODO support non-longitude wrapped geo shapes.
public static SpatialExtentStateWrappedLongitudeState initSingle() { public static SpatialExtentStateWrappedLongitudeState initSingle() {
return new SpatialExtentStateWrappedLongitudeState(); return new SpatialExtentStateWrappedLongitudeState();
} }

View file

@ -18,6 +18,7 @@ import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.lucene.spatial.GeometryDocValueReader;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -53,11 +54,18 @@ final class SpatialExtentGroupingState extends AbstractArrayState {
) { ) {
for (int i = 0; i < selected.getPositionCount(); i++) { for (int i = 0; i < selected.getPositionCount(); i++) {
int group = selected.getInt(i); int group = selected.getInt(i);
assert hasValue(group); if (hasValue(group)) {
minXsBuilder.appendInt(minXs.get(group)); minXsBuilder.appendInt(minXs.get(group));
maxXsBuilder.appendInt(maxXs.get(group)); maxXsBuilder.appendInt(maxXs.get(group));
maxYsBuilder.appendInt(maxYs.get(group)); maxYsBuilder.appendInt(maxYs.get(group));
minYsBuilder.appendInt(minYs.get(group)); minYsBuilder.appendInt(minYs.get(group));
} else {
// TODO: Should we add Nulls here instead?
minXsBuilder.appendInt(Integer.MAX_VALUE);
maxXsBuilder.appendInt(Integer.MIN_VALUE);
maxYsBuilder.appendInt(Integer.MIN_VALUE);
minYsBuilder.appendInt(Integer.MAX_VALUE);
}
} }
blocks[offset + 0] = minXsBuilder.build(); blocks[offset + 0] = minXsBuilder.build();
blocks[offset + 1] = maxXsBuilder.build(); blocks[offset + 1] = maxXsBuilder.build();
@ -66,6 +74,32 @@ final class SpatialExtentGroupingState extends AbstractArrayState {
} }
} }
/**
* This method is used when extents are extracted from the doc-values field by the {@link GeometryDocValueReader}.
* This optimization is enabled when the field has doc-values and is only used in the ST_EXTENT aggregation.
*/
public void add(int groupId, int[] values) {
if (values.length == 6) {
// Values are stored according to the order defined in the Extent class
int top = values[0];
int bottom = values[1];
int negLeft = values[2];
int negRight = values[3];
int posLeft = values[4];
int posRight = values[5];
add(groupId, Math.min(negLeft, posLeft), Math.max(negRight, posRight), top, bottom);
} else if (values.length == 4) {
// Values are stored according to the order defined in the Rectangle class
int minX = values[0];
int maxX = values[1];
int maxY = values[2];
int minY = values[3];
add(groupId, minX, maxX, maxY, minY);
} else {
throw new IllegalArgumentException("Expected 4 or 6 values, got " + values.length);
}
}
public void add(int groupId, Geometry geometry) { public void add(int groupId, Geometry geometry) {
ensureCapacity(groupId); ensureCapacity(groupId);
pointType.computeEnvelope(geometry) pointType.computeEnvelope(geometry)
@ -80,6 +114,10 @@ final class SpatialExtentGroupingState extends AbstractArrayState {
); );
} }
/**
* This method is used when the field is a geo_point or cartesian_point and is loaded from doc-values.
* This optimization is enabled when the field has doc-values and is only used in a spatial aggregation.
*/
public void add(int groupId, long encoded) { public void add(int groupId, long encoded) {
int x = pointType.extractX(encoded); int x = pointType.extractX(encoded);
int y = pointType.extractY(encoded); int y = pointType.extractY(encoded);

View file

@ -19,20 +19,23 @@ import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor; import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.lucene.spatial.GeometryDocValueReader;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import static org.elasticsearch.compute.aggregation.spatial.SpatialExtentStateWrappedLongitudeState.asRectangle;
final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArrayState implements GroupingAggregatorState { final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArrayState implements GroupingAggregatorState {
// Only geo points support longitude wrapping. // Only geo points support longitude wrapping.
private static final PointType POINT_TYPE = PointType.GEO; private static final PointType POINT_TYPE = PointType.GEO;
private IntArray minNegXs; private IntArray tops;
private IntArray minPosXs; private IntArray bottoms;
private IntArray maxNegXs; private IntArray negLefts;
private IntArray maxPosXs; private IntArray negRights;
private IntArray maxYs; private IntArray posLefts;
private IntArray minYs; private IntArray posRights;
private GeoPointEnvelopeVisitor geoPointVisitor = new GeoPointEnvelopeVisitor(); private final SpatialEnvelopeVisitor.GeoPointVisitor geoPointVisitor;
SpatialExtentGroupingStateWrappedLongitudeState() { SpatialExtentGroupingStateWrappedLongitudeState() {
this(BigArrays.NON_RECYCLING_INSTANCE); this(BigArrays.NON_RECYCLING_INSTANCE);
@ -40,44 +43,52 @@ final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArra
SpatialExtentGroupingStateWrappedLongitudeState(BigArrays bigArrays) { SpatialExtentGroupingStateWrappedLongitudeState(BigArrays bigArrays) {
super(bigArrays); super(bigArrays);
this.minNegXs = bigArrays.newIntArray(0, false); this.tops = bigArrays.newIntArray(0, false);
this.minPosXs = bigArrays.newIntArray(0, false); this.bottoms = bigArrays.newIntArray(0, false);
this.maxNegXs = bigArrays.newIntArray(0, false); this.negLefts = bigArrays.newIntArray(0, false);
this.maxPosXs = bigArrays.newIntArray(0, false); this.negRights = bigArrays.newIntArray(0, false);
this.maxYs = bigArrays.newIntArray(0, false); this.posLefts = bigArrays.newIntArray(0, false);
this.minYs = bigArrays.newIntArray(0, false); this.posRights = bigArrays.newIntArray(0, false);
enableGroupIdTracking(new SeenGroupIds.Empty()); enableGroupIdTracking(new SeenGroupIds.Empty());
this.geoPointVisitor = new SpatialEnvelopeVisitor.GeoPointVisitor(SpatialEnvelopeVisitor.WrapLongitude.WRAP);
} }
@Override @Override
public void toIntermediate(Block[] blocks, int offset, IntVector selected, DriverContext driverContext) { public void toIntermediate(Block[] blocks, int offset, IntVector selected, DriverContext driverContext) {
assert blocks.length >= offset; assert blocks.length >= offset;
try ( try (
var minNegXsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var topsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
var minPosXsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var bottomsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
var maxNegXsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var negLeftsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
var maxPosXsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var negRightsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
var maxYsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var posLeftsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
var minYsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount()); var posRightsBuilder = driverContext.blockFactory().newIntBlockBuilder(selected.getPositionCount());
) { ) {
for (int i = 0; i < selected.getPositionCount(); i++) { for (int i = 0; i < selected.getPositionCount(); i++) {
int group = selected.getInt(i); int group = selected.getInt(i);
assert hasValue(group); if (hasValue(group)) {
assert minNegXs.get(group) <= 0 == maxNegXs.get(group) <= 0; topsBuilder.appendInt(tops.get(group));
assert minPosXs.get(group) >= 0 == maxPosXs.get(group) >= 0; bottomsBuilder.appendInt(bottoms.get(group));
minNegXsBuilder.appendInt(minNegXs.get(group)); negLeftsBuilder.appendInt(negLefts.get(group));
minPosXsBuilder.appendInt(minPosXs.get(group)); negRightsBuilder.appendInt(negRights.get(group));
maxNegXsBuilder.appendInt(maxNegXs.get(group)); posLeftsBuilder.appendInt(posLefts.get(group));
maxPosXsBuilder.appendInt(maxPosXs.get(group)); posRightsBuilder.appendInt(posRights.get(group));
maxYsBuilder.appendInt(maxYs.get(group)); } else {
minYsBuilder.appendInt(minYs.get(group)); // TODO: Should we add Nulls here instead?
topsBuilder.appendInt(Integer.MIN_VALUE);
bottomsBuilder.appendInt(Integer.MAX_VALUE);
negLeftsBuilder.appendInt(Integer.MAX_VALUE);
negRightsBuilder.appendInt(Integer.MIN_VALUE);
posLeftsBuilder.appendInt(Integer.MAX_VALUE);
posRightsBuilder.appendInt(Integer.MIN_VALUE);
} }
blocks[offset + 0] = minNegXsBuilder.build(); }
blocks[offset + 1] = minPosXsBuilder.build(); blocks[offset + 0] = topsBuilder.build();
blocks[offset + 2] = maxNegXsBuilder.build(); blocks[offset + 1] = bottomsBuilder.build();
blocks[offset + 3] = maxPosXsBuilder.build(); blocks[offset + 2] = negLeftsBuilder.build();
blocks[offset + 4] = maxYsBuilder.build(); blocks[offset + 3] = negRightsBuilder.build();
blocks[offset + 5] = minYsBuilder.build(); blocks[offset + 4] = posLeftsBuilder.build();
blocks[offset + 5] = posRightsBuilder.build();
} }
} }
@ -87,12 +98,12 @@ final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArra
if (geo.visit(new SpatialEnvelopeVisitor(geoPointVisitor))) { if (geo.visit(new SpatialEnvelopeVisitor(geoPointVisitor))) {
add( add(
groupId, groupId,
SpatialAggregationUtils.encodeNegativeLongitude(geoPointVisitor.getMinNegX()), POINT_TYPE.encoder().encodeY(geoPointVisitor.getTop()),
SpatialAggregationUtils.encodePositiveLongitude(geoPointVisitor.getMinPosX()), POINT_TYPE.encoder().encodeY(geoPointVisitor.getBottom()),
SpatialAggregationUtils.encodeNegativeLongitude(geoPointVisitor.getMaxNegX()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getNegLeft()),
SpatialAggregationUtils.encodePositiveLongitude(geoPointVisitor.getMaxPosX()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getNegRight()),
POINT_TYPE.encoder().encodeY(geoPointVisitor.getMaxY()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getPosLeft()),
POINT_TYPE.encoder().encodeY(geoPointVisitor.getMinY()) SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getPosRight())
); );
} }
} }
@ -102,53 +113,73 @@ final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArra
if (inState.hasValue(inPosition)) { if (inState.hasValue(inPosition)) {
add( add(
groupId, groupId,
inState.minNegXs.get(inPosition), inState.tops.get(inPosition),
inState.minPosXs.get(inPosition), inState.bottoms.get(inPosition),
inState.maxNegXs.get(inPosition), inState.negLefts.get(inPosition),
inState.maxPosXs.get(inPosition), inState.negRights.get(inPosition),
inState.maxYs.get(inPosition), inState.posLefts.get(inPosition),
inState.minYs.get(inPosition) inState.posRights.get(inPosition)
); );
} }
} }
/**
* This method is used when the field is a geo_point or cartesian_point and is loaded from doc-values.
* This optimization is enabled when the field has doc-values and is only used in a spatial aggregation.
*/
public void add(int groupId, long encoded) { public void add(int groupId, long encoded) {
int x = POINT_TYPE.extractX(encoded); int x = POINT_TYPE.extractX(encoded);
int y = POINT_TYPE.extractY(encoded); int y = POINT_TYPE.extractY(encoded);
add(groupId, x, x, x, x, y, y); add(groupId, y, y, x, x, x, x);
} }
public void add(int groupId, int minNegX, int minPosX, int maxNegX, int maxPosX, int maxY, int minY) { /**
* This method is used when extents are extracted from the doc-values field by the {@link GeometryDocValueReader}.
* This optimization is enabled when the field has doc-values and is only used in the ST_EXTENT aggregation.
*/
public void add(int groupId, int[] values) {
if (values.length != 6) {
throw new IllegalArgumentException("Expected 6 values, got " + values.length);
}
// Values are stored according to the order defined in the Extent class
int top = values[0];
int bottom = values[1];
int negLeft = values[2];
int negRight = values[3];
int posLeft = values[4];
int posRight = values[5];
add(groupId, top, bottom, negLeft, negRight, posLeft, posRight);
}
public void add(int groupId, int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) {
ensureCapacity(groupId); ensureCapacity(groupId);
if (hasValue(groupId)) { if (hasValue(groupId)) {
minNegXs.set(groupId, Math.min(minNegXs.get(groupId), minNegX)); tops.set(groupId, Math.max(tops.get(groupId), top));
minPosXs.set(groupId, SpatialAggregationUtils.minPos(minPosXs.get(groupId), minPosX)); bottoms.set(groupId, Math.min(bottoms.get(groupId), bottom));
maxNegXs.set(groupId, SpatialAggregationUtils.maxNeg(maxNegXs.get(groupId), maxNegX)); negLefts.set(groupId, Math.min(negLefts.get(groupId), negLeft));
maxPosXs.set(groupId, Math.max(maxPosXs.get(groupId), maxPosX)); negRights.set(groupId, SpatialAggregationUtils.maxNeg(negRights.get(groupId), negRight));
maxYs.set(groupId, Math.max(maxYs.get(groupId), maxY)); posLefts.set(groupId, SpatialAggregationUtils.minPos(posLefts.get(groupId), posLeft));
minYs.set(groupId, Math.min(minYs.get(groupId), minY)); posRights.set(groupId, Math.max(posRights.get(groupId), posRight));
} else { } else {
minNegXs.set(groupId, minNegX); tops.set(groupId, top);
minPosXs.set(groupId, minPosX); bottoms.set(groupId, bottom);
maxNegXs.set(groupId, maxNegX); negLefts.set(groupId, negLeft);
maxPosXs.set(groupId, maxPosX); negRights.set(groupId, negRight);
maxYs.set(groupId, maxY); posLefts.set(groupId, posLeft);
minYs.set(groupId, minY); posRights.set(groupId, posRight);
} }
assert minNegX <= 0 == maxNegX <= 0 : "minNegX=" + minNegX + " maxNegX=" + maxNegX;
assert minPosX >= 0 == maxPosX >= 0 : "minPosX=" + minPosX + " maxPosX=" + maxPosX;
trackGroupId(groupId); trackGroupId(groupId);
} }
private void ensureCapacity(int groupId) { private void ensureCapacity(int groupId) {
long requiredSize = groupId + 1; long requiredSize = groupId + 1;
if (minNegXs.size() < requiredSize) { if (negLefts.size() < requiredSize) {
minNegXs = bigArrays.grow(minNegXs, requiredSize); tops = bigArrays.grow(tops, requiredSize);
minPosXs = bigArrays.grow(minPosXs, requiredSize); bottoms = bigArrays.grow(bottoms, requiredSize);
maxNegXs = bigArrays.grow(maxNegXs, requiredSize); negLefts = bigArrays.grow(negLefts, requiredSize);
maxPosXs = bigArrays.grow(maxPosXs, requiredSize); negRights = bigArrays.grow(negRights, requiredSize);
minYs = bigArrays.grow(minYs, requiredSize); posLefts = bigArrays.grow(posLefts, requiredSize);
maxYs = bigArrays.grow(maxYs, requiredSize); posRights = bigArrays.grow(posRights, requiredSize);
} }
} }
@ -160,13 +191,13 @@ final class SpatialExtentGroupingStateWrappedLongitudeState extends AbstractArra
builder.appendBytesRef( builder.appendBytesRef(
new BytesRef( new BytesRef(
WellKnownBinary.toWKB( WellKnownBinary.toWKB(
SpatialAggregationUtils.asRectangle( asRectangle(
minNegXs.get(si), tops.get(si),
minPosXs.get(si), bottoms.get(si),
maxNegXs.get(si), negLefts.get(si),
maxPosXs.get(si), negRights.get(si),
maxYs.get(si), posLefts.get(si),
minYs.get(si) posRights.get(si)
), ),
ByteOrder.LITTLE_ENDIAN ByteOrder.LITTLE_ENDIAN
) )

View file

@ -16,27 +16,27 @@ import org.elasticsearch.compute.operator.DriverContext;
abstract class SpatialExtentLongitudeWrappingAggregator { abstract class SpatialExtentLongitudeWrappingAggregator {
public static void combineIntermediate( public static void combineIntermediate(
SpatialExtentStateWrappedLongitudeState current, SpatialExtentStateWrappedLongitudeState current,
int minNegX, int top,
int minPosX, int bottom,
int maxNegX, int negLeft,
int maxPosX, int negRight,
int maxY, int posLeft,
int minY int posRight
) { ) {
current.add(minNegX, minPosX, maxNegX, maxPosX, maxY, minY); current.add(top, bottom, negLeft, negRight, posLeft, posRight);
} }
public static void combineIntermediate( public static void combineIntermediate(
SpatialExtentGroupingStateWrappedLongitudeState current, SpatialExtentGroupingStateWrappedLongitudeState current,
int groupId, int groupId,
int minNegX, int top,
int minPosX, int bottom,
int maxNegX, int negLeft,
int maxPosX, int negRight,
int maxY, int posLeft,
int minY int posRight
) { ) {
current.add(groupId, minNegX, minPosX, maxNegX, maxPosX, maxY, minY); current.add(groupId, top, bottom, negLeft, negRight, posLeft, posRight);
} }
public static Block evaluateFinal(SpatialExtentStateWrappedLongitudeState state, DriverContext driverContext) { public static Block evaluateFinal(SpatialExtentStateWrappedLongitudeState state, DriverContext driverContext) {

View file

@ -15,6 +15,7 @@ import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle; import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.lucene.spatial.CoordinateEncoder; import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.lucene.spatial.GeometryDocValueReader;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -55,6 +56,32 @@ final class SpatialExtentState implements AggregatorState {
); );
} }
/**
* This method is used when extents are extracted from the doc-values field by the {@link GeometryDocValueReader}.
* This optimization is enabled when the field has doc-values and is only used in the ST_EXTENT aggregation.
*/
public void add(int[] values) {
if (values.length == 6) {
// Values are stored according to the order defined in the Extent class
int top = values[0];
int bottom = values[1];
int negLeft = values[2];
int negRight = values[3];
int posLeft = values[4];
int posRight = values[5];
add(Math.min(negLeft, posLeft), Math.max(negRight, posRight), top, bottom);
} else if (values.length == 4) {
// Values are stored according to the order defined in the Rectangle class
int minX = values[0];
int maxX = values[1];
int maxY = values[2];
int minY = values[3];
add(minX, maxX, maxY, minY);
} else {
throw new IllegalArgumentException("Expected 4 or 6 values, got " + values.length);
}
}
public void add(int minX, int maxX, int maxY, int minY) { public void add(int minX, int maxX, int maxY, int minY) {
seen = true; seen = true;
this.minX = Math.min(this.minX, minX); this.minX = Math.min(this.minX, minX);
@ -63,6 +90,10 @@ final class SpatialExtentState implements AggregatorState {
this.minY = Math.min(this.minY, minY); this.minY = Math.min(this.minY, minY);
} }
/**
* This method is used when the field is a geo_point or cartesian_point and is loaded from doc-values.
* This optimization is enabled when the field has doc-values and is only used in a spatial aggregation.
*/
public void add(long encoded) { public void add(long encoded) {
int x = pointType.extractX(encoded); int x = pointType.extractX(encoded);
int y = pointType.extractY(encoded); int y = pointType.extractY(encoded);

View file

@ -7,28 +7,35 @@
package org.elasticsearch.compute.aggregation.spatial; package org.elasticsearch.compute.aggregation.spatial;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.compute.aggregation.AggregatorState; import org.elasticsearch.compute.aggregation.AggregatorState;
import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor; import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.geometry.utils.WellKnownBinary; import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.lucene.spatial.GeometryDocValueReader;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import static org.elasticsearch.compute.aggregation.spatial.SpatialAggregationUtils.decodeLongitude;
final class SpatialExtentStateWrappedLongitudeState implements AggregatorState { final class SpatialExtentStateWrappedLongitudeState implements AggregatorState {
// Only geo points support longitude wrapping. // Only geo points support longitude wrapping.
private static final PointType POINT_TYPE = PointType.GEO; private static final PointType POINT_TYPE = PointType.GEO;
private boolean seen = false; private boolean seen = false;
private int minNegX = SpatialAggregationUtils.DEFAULT_NEG; private int top = Integer.MIN_VALUE;
private int minPosX = SpatialAggregationUtils.DEFAULT_POS; private int bottom = Integer.MAX_VALUE;
private int maxNegX = SpatialAggregationUtils.DEFAULT_NEG; private int negLeft = Integer.MAX_VALUE;
private int maxPosX = SpatialAggregationUtils.DEFAULT_POS; private int negRight = Integer.MIN_VALUE;
private int maxY = Integer.MIN_VALUE; private int posLeft = Integer.MAX_VALUE;
private int minY = Integer.MAX_VALUE; private int posRight = Integer.MIN_VALUE;
private GeoPointEnvelopeVisitor geoPointVisitor = new GeoPointEnvelopeVisitor(); private final SpatialEnvelopeVisitor.GeoPointVisitor geoPointVisitor = new SpatialEnvelopeVisitor.GeoPointVisitor(
SpatialEnvelopeVisitor.WrapLongitude.WRAP
);
@Override @Override
public void close() {} public void close() {}
@ -37,44 +44,64 @@ final class SpatialExtentStateWrappedLongitudeState implements AggregatorState {
public void toIntermediate(Block[] blocks, int offset, DriverContext driverContext) { public void toIntermediate(Block[] blocks, int offset, DriverContext driverContext) {
assert blocks.length >= offset + 6; assert blocks.length >= offset + 6;
var blockFactory = driverContext.blockFactory(); var blockFactory = driverContext.blockFactory();
blocks[offset + 0] = blockFactory.newConstantIntBlockWith(minNegX, 1); blocks[offset + 0] = blockFactory.newConstantIntBlockWith(top, 1);
blocks[offset + 1] = blockFactory.newConstantIntBlockWith(minPosX, 1); blocks[offset + 1] = blockFactory.newConstantIntBlockWith(bottom, 1);
blocks[offset + 2] = blockFactory.newConstantIntBlockWith(maxNegX, 1); blocks[offset + 2] = blockFactory.newConstantIntBlockWith(negLeft, 1);
blocks[offset + 3] = blockFactory.newConstantIntBlockWith(maxPosX, 1); blocks[offset + 3] = blockFactory.newConstantIntBlockWith(negRight, 1);
blocks[offset + 4] = blockFactory.newConstantIntBlockWith(maxY, 1); blocks[offset + 4] = blockFactory.newConstantIntBlockWith(posLeft, 1);
blocks[offset + 5] = blockFactory.newConstantIntBlockWith(minY, 1); blocks[offset + 5] = blockFactory.newConstantIntBlockWith(posRight, 1);
} }
public void add(Geometry geo) { public void add(Geometry geo) {
geoPointVisitor.reset(); geoPointVisitor.reset();
if (geo.visit(new SpatialEnvelopeVisitor(geoPointVisitor))) { if (geo.visit(new SpatialEnvelopeVisitor(geoPointVisitor))) {
add( add(
SpatialAggregationUtils.encodeNegativeLongitude(geoPointVisitor.getMinNegX()), POINT_TYPE.encoder().encodeY(geoPointVisitor.getTop()),
SpatialAggregationUtils.encodePositiveLongitude(geoPointVisitor.getMinPosX()), POINT_TYPE.encoder().encodeY(geoPointVisitor.getBottom()),
SpatialAggregationUtils.encodeNegativeLongitude(geoPointVisitor.getMaxNegX()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getNegLeft()),
SpatialAggregationUtils.encodePositiveLongitude(geoPointVisitor.getMaxPosX()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getNegRight()),
POINT_TYPE.encoder().encodeY(geoPointVisitor.getMaxY()), SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getPosLeft()),
POINT_TYPE.encoder().encodeY(geoPointVisitor.getMinY()) SpatialAggregationUtils.encodeLongitude(geoPointVisitor.getPosRight())
); );
} }
} }
public void add(int minNegX, int minPosX, int maxNegX, int maxPosX, int maxY, int minY) { /**
seen = true; * This method is used when extents are extracted from the doc-values field by the {@link GeometryDocValueReader}.
this.minNegX = Math.min(this.minNegX, minNegX); * This optimization is enabled when the field has doc-values and is only used in the ST_EXTENT aggregation.
this.minPosX = SpatialAggregationUtils.minPos(this.minPosX, minPosX); */
this.maxNegX = SpatialAggregationUtils.maxNeg(this.maxNegX, maxNegX); public void add(int[] values) {
this.maxPosX = Math.max(this.maxPosX, maxPosX); if (values.length != 6) {
this.maxY = Math.max(this.maxY, maxY); throw new IllegalArgumentException("Expected 6 values, got " + values.length);
this.minY = Math.min(this.minY, minY); }
assert this.minNegX <= 0 == this.maxNegX <= 0 : "minNegX=" + this.minNegX + " maxNegX=" + this.maxNegX; // Values are stored according to the order defined in the Extent class
assert this.minPosX >= 0 == this.maxPosX >= 0 : "minPosX=" + this.minPosX + " maxPosX=" + this.maxPosX; int top = values[0];
int bottom = values[1];
int negLeft = values[2];
int negRight = values[3];
int posLeft = values[4];
int posRight = values[5];
add(top, bottom, negLeft, negRight, posLeft, posRight);
} }
public void add(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) {
seen = true;
this.top = Math.max(this.top, top);
this.bottom = Math.min(this.bottom, bottom);
this.negLeft = Math.min(this.negLeft, negLeft);
this.negRight = SpatialAggregationUtils.maxNeg(this.negRight, negRight);
this.posLeft = SpatialAggregationUtils.minPos(this.posLeft, posLeft);
this.posRight = Math.max(this.posRight, posRight);
}
/**
* This method is used when the field is a geo_point or cartesian_point and is loaded from doc-values.
* This optimization is enabled when the field has doc-values and is only used in a spatial aggregation.
*/
public void add(long encoded) { public void add(long encoded) {
int x = POINT_TYPE.extractX(encoded); int x = POINT_TYPE.extractX(encoded);
int y = POINT_TYPE.extractY(encoded); int y = POINT_TYPE.extractY(encoded);
add(x, x, x, x, y, y); add(y, y, x, x, x, x);
} }
public Block toBlock(DriverContext driverContext) { public Block toBlock(DriverContext driverContext) {
@ -83,9 +110,18 @@ final class SpatialExtentStateWrappedLongitudeState implements AggregatorState {
} }
private byte[] toWKB() { private byte[] toWKB() {
return WellKnownBinary.toWKB( return WellKnownBinary.toWKB(asRectangle(top, bottom, negLeft, negRight, posLeft, posRight), ByteOrder.LITTLE_ENDIAN);
SpatialAggregationUtils.asRectangle(minNegX, minPosX, maxNegX, maxPosX, maxY, minY), }
ByteOrder.LITTLE_ENDIAN
static Rectangle asRectangle(int top, int bottom, int negLeft, int negRight, int posLeft, int posRight) {
return SpatialEnvelopeVisitor.GeoPointVisitor.getResult(
GeoEncodingUtils.decodeLatitude(top),
GeoEncodingUtils.decodeLatitude(bottom),
negLeft <= 0 ? decodeLongitude(negLeft) : Double.POSITIVE_INFINITY,
negRight <= 0 ? decodeLongitude(negRight) : Double.NEGATIVE_INFINITY,
posLeft >= 0 ? decodeLongitude(posLeft) : Double.POSITIVE_INFINITY,
posRight >= 0 ? decodeLongitude(posRight) : Double.NEGATIVE_INFINITY,
SpatialEnvelopeVisitor.WrapLongitude.WRAP
); );
} }
} }

View file

@ -524,6 +524,8 @@ POINT (42.97109629958868 14.7552534006536) | 1
stExtentSingleGeoPoint stExtentSingleGeoPoint
required_capability: st_extent_agg required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
ROW point = TO_GEOPOINT("POINT(42.97109629958868 14.7552534006536)") ROW point = TO_GEOPOINT("POINT(42.97109629958868 14.7552534006536)")
| STATS extent = ST_EXTENT_AGG(point) | STATS extent = ST_EXTENT_AGG(point)
; ;
@ -534,6 +536,8 @@ BBOX(42.97109629958868, 42.97109629958868, 14.7552534006536, 14.7552534006536)
stExtentMultipleGeoPoints stExtentMultipleGeoPoints
required_capability: st_extent_agg required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
// tag::st_extent_agg-airports[] // tag::st_extent_agg-airports[]
FROM airports FROM airports
| WHERE country == "India" | WHERE country == "India"
@ -547,35 +551,257 @@ BBOX (70.77995480038226, 91.5882289968431, 33.9830909203738, 8.47650992218405)
// end::st_extent_agg-airports-result[] // end::st_extent_agg-airports-result[]
; ;
stExtentMultipleGeoPointsNoDocValues stExtentMultipleGeoPointsCount
required_capability: st_extent_agg required_capability: st_extent_agg
FROM airports_no_doc_values | WHERE country == "India" | STATS extent = ST_EXTENT_AGG(location) required_capability: st_extent_agg_docvalues
FROM airports
| WHERE country == "India"
| STATS extent = ST_EXTENT_AGG(location), count = COUNT()
; ;
extent:geo_shape extent:geo_shape | count:long
BBOX (70.77995480038226, 91.5882289968431, 33.9830909203738, 8.47650992218405) BBOX (70.77995480038226, 91.5882289968431, 33.9830909203738, 8.47650992218405) | 50
;
stExtentMultipleGeoPointsCountNoDocValues
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airports_no_doc_values
| WHERE country == "India"
| STATS extent = ST_EXTENT_AGG(location), count = COUNT()
;
extent:geo_shape | count:long
BBOX (70.77995480038226, 91.5882289968431, 33.9830909203738, 8.47650992218405) | 50
; ;
stExtentMultipleGeoPointGrouping stExtentMultipleGeoPointGrouping
required_capability: st_extent_agg required_capability: st_extent_agg
FROM airports | STATS extent = ST_EXTENT_AGG(location) BY country | SORT country | LIMIT 3 required_capability: st_extent_agg_docvalues
FROM airports
| STATS extent = ST_EXTENT_AGG(location), count = COUNT() BY country
| SORT count DESC, country ASC
| LIMIT 5
; ;
extent:geo_shape | country:keyword extent:geo_shape | count:long | country:keyword
BBOX (69.2100736219436, 69.2100736219436, 34.56339786294848, 34.56339786294848) | Afghanistan BBOX (-159.34908430092037, -71.01640669628978, 64.81809809803963, 19.71479767933488) | 129 | United States
BBOX (19.715032372623682, 19.715032372623682, 41.4208514476195, 41.4208514476195) | Albania BBOX (70.77995480038226, 91.5882289968431, 33.9830909203738, 8.47650992218405) | 50 | India
BBOX (-0.6067969836294651, 6.621946580708027, 36.69972063973546, 35.62027471605688) | Algeria BBOX (-117.19751106575131, -86.87441730871797, 32.833958650007844, 14.791128113865852) | 45 | Mexico
BBOX (76.01301474496722, 130.45620465651155, 46.84301500674337, 18.309095981530845) | 41 | China
BBOX (-135.07621010765433, -52.743333745747805, 63.751152316108346, 43.163360520266) | 37 | Canada
; ;
stExtentGeoShapes stExtentGeoShapes
required_capability: st_extent_agg required_capability: st_extent_agg
FROM airport_city_boundaries | WHERE region == "City of New York" | STATS extent = ST_EXTENT_AGG(city_boundary) required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| STATS extent = ST_EXTENT_AGG(city_boundary)
; ;
extent:geo_shape extent:geo_shape
BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321) BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321)
; ;
stExtentGeoPoints
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| STATS extent = ST_EXTENT_AGG(city_location)
;
extent:geo_shape
BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
;
stExtentGeoShapesAndPoints
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| STATS extent_shapes = ST_EXTENT_AGG(city_boundary), extent_points = ST_EXTENT_AGG(city_location)
;
extent_shapes:geo_shape | extent_points:geo_shape
BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321) | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
;
stExtentGeoShapesGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_boundary) BY prefix
| KEEP prefix, extent
| SORT prefix ASC
;
prefix:keyword | extent:geo_shape
E | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321)
J | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321)
L | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321)
;
stExtentGeoPointsGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_location) BY prefix
| KEEP prefix, extent
| SORT prefix ASC
;
prefix:keyword | extent:geo_shape
E | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
J | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
L | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
;
stExtentGeoShapesAndPointsGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| WHERE region == "City of New York"
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent_shapes = ST_EXTENT_AGG(city_boundary), extent_points = ST_EXTENT_AGG(city_location) BY prefix
| KEEP prefix, extent_shapes, extent_points
| SORT prefix ASC
;
prefix:keyword | extent_shapes:geo_shape | extent_points:geo_shape
E | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321) | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
J | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321) | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
L | BBOX (-74.25880000926554, -73.70020005851984, 40.91759996954352, 40.47659996431321) | BBOX (-73.92490002326667, -73.92490002326667, 40.69429999217391, 40.69429999217391)
;
stExtentManyGeoShapesGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_boundary) BY prefix
| KEEP prefix, extent
| SORT prefix
| LIMIT 3
;
prefix:keyword | extent:geo_shape
A | BBOX (-171.91890003159642, 175.90319998562336, 64.61419996339828, -37.36450002528727)
B | BBOX (-116.51340007781982, 153.2021999359131, 60.631899973377585, -41.20620000176132)
C | BBOX (-107.51820000819862, 172.6055999379605, 55.732699991203845, -43.90400002710521)
;
stExtentManyGeoPointsGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_location) BY prefix
| KEEP prefix, extent
| SORT prefix
| LIMIT 3
;
prefix:keyword | extent:geo_shape
A | BBOX (-171.75000007264316, 174.73999994806945, 64.54999999143183, -36.84060002211481)
B | BBOX (-116.23080002143979, 153.02809992805123, 60.46669999603182, -41.1500000115484)
C | BBOX (-107.39390007220209, 172.38329996354878, 55.676099974662066, -43.58330002985895)
;
stExtentManyGeoShapesAndPointsGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airport_city_boundaries
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent_shapes = ST_EXTENT_AGG(city_boundary), extent_points = ST_EXTENT_AGG(city_location) BY prefix
| KEEP prefix, extent_shapes, extent_points
| SORT prefix
| LIMIT 3
;
prefix:keyword | extent_shapes:geo_shape | extent_points:geo_shape
A | BBOX (-171.91890003159642, 175.90319998562336, 64.61419996339828, -37.36450002528727) | BBOX (-171.75000007264316, 174.73999994806945, 64.54999999143183, -36.84060002211481)
B | BBOX (-116.51340007781982, 153.2021999359131, 60.631899973377585, -41.20620000176132) | BBOX (-116.23080002143979, 153.02809992805123, 60.46669999603182, -41.1500000115484)
C | BBOX (-107.51820000819862, 172.6055999379605, 55.732699991203845, -43.90400002710521) | BBOX (-107.39390007220209, 172.38329996354878, 55.676099974662066, -43.58330002985895)
;
stExtentManyGeoShapesGroupedEnrich
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
required_capability: enrich_load
FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_boundary), count = COUNT() BY prefix
| KEEP prefix, count, extent
| SORT count DESC, prefix ASC
| LIMIT 3
;
prefix:keyword | count:long | extent:geo_shape
S | 77 | BBOX (-136.45440001040697, 178.8686999771744, 61.38089996762574, -33.92440003808588)
C | 75 | BBOX (-107.51820000819862, 172.6055999379605, 55.732699991203845, -43.90400002710521)
B | 69 | BBOX (-116.51340007781982, 153.2021999359131, 60.631899973377585, -41.20620000176132)
;
stExtentManyGeoPointsGroupedEnrich
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
required_capability: enrich_load
FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent = ST_EXTENT_AGG(city_location), count = COUNT() BY prefix
| KEEP prefix, count, extent
| SORT count DESC, prefix ASC
| LIMIT 3
;
prefix:keyword | count:long | extent:geo_shape
S | 77 | BBOX (-135.3152000438422, 178.54539999738336, 69.21669997740537, -33.8678000215441)
C | 75 | BBOX (-107.39390007220209, 172.38329996354878, 55.676099974662066, -43.58330002985895)
B | 69 | BBOX (-116.23080002143979, 153.02809992805123, 60.46669999603182, -41.1500000115484)
;
stExtentManyGeoShapesAndPointsGroupedEnrich
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
required_capability: enrich_load
FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| EVAL prefix = SUBSTRING(abbrev, 1, 1)
| STATS extent_shapes = ST_EXTENT_AGG(city_boundary), extent_points = ST_EXTENT_AGG(city_location), count = COUNT() BY prefix
| KEEP prefix, count, extent_shapes, extent_points
| SORT count DESC, prefix ASC
| LIMIT 3
;
prefix:keyword | count:long | extent_shapes:geo_shape | extent_points:geo_shape
S | 77 | BBOX (-136.45440001040697, 178.8686999771744, 61.38089996762574, -33.92440003808588) | BBOX (-135.3152000438422, 178.54539999738336, 69.21669997740537, -33.8678000215441)
C | 75 | BBOX (-107.51820000819862, 172.6055999379605, 55.732699991203845, -43.90400002710521) | BBOX (-107.39390007220209, 172.38329996354878, 55.676099974662066, -43.58330002985895)
B | 69 | BBOX (-116.51340007781982, 153.2021999359131, 60.631899973377585, -41.20620000176132) | BBOX (-116.23080002143979, 153.02809992805123, 60.46669999603182, -41.1500000115484)
;
############################################### ###############################################
# Tests for ST_INTERSECTS on GEO_POINT type # Tests for ST_INTERSECTS on GEO_POINT type
@ -1777,6 +2003,18 @@ extent:cartesian_shape
BBOX (4783520.5, 1.6168486E7, 8704352.0, -584415.9375) BBOX (4783520.5, 1.6168486E7, 8704352.0, -584415.9375)
; ;
stExtentMultipleCartesianPointsCount
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM airports_web
| STATS extent = ST_EXTENT_AGG(location), count = COUNT()
;
extent:cartesian_shape | count:long
BBOX (-1.949601E7, 1.9947946E7, 1.4502138E7, -7128878.5) | 849
;
stExtentMultipleCartesianPointGrouping stExtentMultipleCartesianPointGrouping
required_capability: st_extent_agg required_capability: st_extent_agg
FROM airports_web | STATS extent = ST_EXTENT_AGG(location) BY scalerank | SORT scalerank DESC | LIMIT 3 FROM airports_web | STATS extent = ST_EXTENT_AGG(location) BY scalerank | SORT scalerank DESC | LIMIT 3
@ -1838,6 +2076,42 @@ count:long | key:keyword | extent:cartesian_shape
4 | Fou | BBOX (0.0, 3.0, 3.0, 0.0) 4 | Fou | BBOX (0.0, 3.0, 3.0, 0.0)
; ;
stExtentManyCartesianShapesGrouped
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM countries_bbox_web
| EVAL prefix = SUBSTRING(id, 1, 1)
| STATS extent = ST_EXTENT_AGG(shape) BY prefix
| KEEP prefix, extent
| SORT prefix
| LIMIT 3
;
prefix:keyword | extent:cartesian_shape
A | BBOX (-2.0037508E7, 2.0037508E7, 6278042.5, -4.748140544E9)
B | BBOX (-9931524.0, 1.2841846E7, 7591831.0, -3994093.25)
C | BBOX (-1.8462154E7, 1.5002357E7, 1.7926778E7, -7538976.5)
;
stExtentManyCartesianShapesGroupedCount
required_capability: st_extent_agg
required_capability: st_extent_agg_docvalues
FROM countries_bbox_web
| EVAL prefix = SUBSTRING(id, 1, 1)
| STATS extent = ST_EXTENT_AGG(shape), count = COUNT() BY prefix
| KEEP prefix, count, extent
| SORT prefix
| LIMIT 3
;
prefix:keyword | count:long | extent:cartesian_shape
A | 17 | BBOX (-2.0037508E7, 2.0037508E7, 6278042.5, -4.748140544E9)
B | 18 | BBOX (-9931524.0, 1.2841846E7, 7591831.0, -3994093.25)
C | 19 | BBOX (-1.8462154E7, 1.5002357E7, 1.7926778E7, -7538976.5)
;
############################################### ###############################################
# Tests for ST_INTERSECTS on CARTESIAN_POINT type # Tests for ST_INTERSECTS on CARTESIAN_POINT type

View file

@ -296,9 +296,12 @@ public class EsqlCapabilities {
*/ */
ST_DISTANCE, ST_DISTANCE,
/** Support for function {@code ST_EXTENT}. */ /** Support for function {@code ST_EXTENT_AGG}. */
ST_EXTENT_AGG, ST_EXTENT_AGG,
/** Optimization of ST_EXTENT_AGG with doc-values as IntBlock. */
ST_EXTENT_AGG_DOCVALUES,
/** /**
* Fix determination of CRS types in spatial functions when folding. * Fix determination of CRS types in spatial functions when folding.
*/ */

View file

@ -39,7 +39,7 @@ public abstract class SpatialAggregateFunction extends AggregateFunction impleme
this.fieldExtractPreference = fieldExtractPreference; this.fieldExtractPreference = fieldExtractPreference;
} }
public abstract SpatialAggregateFunction withDocValues(); public abstract SpatialAggregateFunction withFieldExtractPreference(FieldExtractPreference preference);
@Override @Override
public boolean licenseCheck(XPackLicenseState state) { public boolean licenseCheck(XPackLicenseState state) {

View file

@ -71,8 +71,8 @@ public class SpatialCentroid extends SpatialAggregateFunction implements ToAggre
} }
@Override @Override
public SpatialCentroid withDocValues() { public SpatialCentroid withFieldExtractPreference(FieldExtractPreference preference) {
return new SpatialCentroid(source(), field(), filter(), FieldExtractPreference.DOC_VALUES); return new SpatialCentroid(source(), field(), filter(), preference);
} }
@Override @Override

View file

@ -11,10 +11,12 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianPointDocValuesAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianPointDocValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianPointSourceValuesAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianPointSourceValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianShapeAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianShapeDocValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentCartesianShapeSourceValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoPointDocValuesAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoPointDocValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoPointSourceValuesAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoPointSourceValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoShapeAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoShapeDocValuesAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.spatial.SpatialExtentGeoShapeSourceValuesAggregatorFunctionSupplier;
import org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference; import org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference;
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
@ -75,8 +77,8 @@ public final class SpatialExtent extends SpatialAggregateFunction implements ToA
} }
@Override @Override
public org.elasticsearch.xpack.esql.expression.function.aggregate.SpatialExtent withDocValues() { public SpatialExtent withFieldExtractPreference(FieldExtractPreference preference) {
return new SpatialExtent(source(), field(), filter(), FieldExtractPreference.DOC_VALUES); return new SpatialExtent(source(), field(), filter(), preference);
} }
@Override @Override
@ -101,7 +103,8 @@ public final class SpatialExtent extends SpatialAggregateFunction implements ToA
@Override @Override
public AggregatorFunctionSupplier supplier(List<Integer> inputChannels) { public AggregatorFunctionSupplier supplier(List<Integer> inputChannels) {
return switch (field().dataType()) { DataType type = field().dataType();
return switch (type) {
case DataType.GEO_POINT -> switch (fieldExtractPreference) { case DataType.GEO_POINT -> switch (fieldExtractPreference) {
case DOC_VALUES -> new SpatialExtentGeoPointDocValuesAggregatorFunctionSupplier(inputChannels); case DOC_VALUES -> new SpatialExtentGeoPointDocValuesAggregatorFunctionSupplier(inputChannels);
case NONE, EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentGeoPointSourceValuesAggregatorFunctionSupplier(inputChannels); case NONE, EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentGeoPointSourceValuesAggregatorFunctionSupplier(inputChannels);
@ -110,10 +113,17 @@ public final class SpatialExtent extends SpatialAggregateFunction implements ToA
case DOC_VALUES -> new SpatialExtentCartesianPointDocValuesAggregatorFunctionSupplier(inputChannels); case DOC_VALUES -> new SpatialExtentCartesianPointDocValuesAggregatorFunctionSupplier(inputChannels);
case NONE, EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentCartesianPointSourceValuesAggregatorFunctionSupplier(inputChannels); case NONE, EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentCartesianPointSourceValuesAggregatorFunctionSupplier(inputChannels);
}; };
// Shapes don't differentiate between source and doc values. case DataType.GEO_SHAPE -> switch (fieldExtractPreference) {
case DataType.GEO_SHAPE -> new SpatialExtentGeoShapeAggregatorFunctionSupplier(inputChannels); case EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentGeoShapeDocValuesAggregatorFunctionSupplier(inputChannels);
case DataType.CARTESIAN_SHAPE -> new SpatialExtentCartesianShapeAggregatorFunctionSupplier(inputChannels); case NONE -> new SpatialExtentGeoShapeSourceValuesAggregatorFunctionSupplier(inputChannels);
default -> throw EsqlIllegalArgumentException.illegalDataType(field().dataType()); case DOC_VALUES -> throw new EsqlIllegalArgumentException("Illegal field extract preference: " + fieldExtractPreference);
};
case DataType.CARTESIAN_SHAPE -> switch (fieldExtractPreference) {
case EXTRACT_SPATIAL_BOUNDS -> new SpatialExtentCartesianShapeDocValuesAggregatorFunctionSupplier(inputChannels);
case NONE -> new SpatialExtentCartesianShapeSourceValuesAggregatorFunctionSupplier(inputChannels);
case DOC_VALUES -> throw new EsqlIllegalArgumentException("Illegal field extract preference: " + fieldExtractPreference);
};
default -> throw EsqlIllegalArgumentException.illegalDataType(type);
}; };
} }
} }

View file

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local; package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Alias;
import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.Expression;
@ -84,7 +85,9 @@ public class SpatialDocValuesExtraction extends PhysicalOptimizerRules.Parameter
// We need to both mark the field to load differently, and change the spatial function to know to use it // We need to both mark the field to load differently, and change the spatial function to know to use it
foundAttributes.add(fieldAttribute); foundAttributes.add(fieldAttribute);
changedAggregates = true; changedAggregates = true;
orderedAggregates.add(as.replaceChild(af.withDocValues())); orderedAggregates.add(
as.replaceChild(af.withFieldExtractPreference(MappedFieldType.FieldExtractPreference.DOC_VALUES))
);
} else { } else {
orderedAggregates.add(aggExpr); orderedAggregates.add(aggExpr);
} }

View file

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local; package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.lucene.spatial.GeometryDocValueWriter; import org.elasticsearch.lucene.spatial.GeometryDocValueWriter;
import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Alias;
import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.Attribute;
@ -49,9 +50,20 @@ import java.util.stream.Collectors;
public class SpatialShapeBoundsExtraction extends ParameterizedOptimizerRule<AggregateExec, LocalPhysicalOptimizerContext> { public class SpatialShapeBoundsExtraction extends ParameterizedOptimizerRule<AggregateExec, LocalPhysicalOptimizerContext> {
@Override @Override
protected PhysicalPlan rule(AggregateExec aggregate, LocalPhysicalOptimizerContext ctx) { protected PhysicalPlan rule(AggregateExec aggregate, LocalPhysicalOptimizerContext ctx) {
var foundAttributes = new HashSet<Attribute>(); Set<Attribute> foundAttributes = findSpatialShapeBoundsAttributes(aggregate, ctx);
if (foundAttributes.isEmpty()) {
return aggregate;
}
return aggregate.transformDown(PhysicalPlan.class, exec -> switch (exec) {
case AggregateExec agg -> transformAggregateExec(agg, foundAttributes);
case FieldExtractExec fieldExtractExec -> transformFieldExtractExec(fieldExtractExec, foundAttributes);
default -> exec;
});
}
return aggregate.transformDown(UnaryExec.class, exec -> { private static Set<Attribute> findSpatialShapeBoundsAttributes(AggregateExec aggregate, LocalPhysicalOptimizerContext ctx) {
var foundAttributes = new HashSet<Attribute>();
aggregate.transformDown(UnaryExec.class, exec -> {
switch (exec) { switch (exec) {
case AggregateExec agg -> { case AggregateExec agg -> {
List<AggregateFunction> aggregateFunctions = agg.aggregates() List<AggregateFunction> aggregateFunctions = agg.aggregates()
@ -84,18 +96,27 @@ public class SpatialShapeBoundsExtraction extends ParameterizedOptimizerRule<Agg
} }
case EvalExec evalExec -> foundAttributes.removeAll(evalExec.references()); case EvalExec evalExec -> foundAttributes.removeAll(evalExec.references());
case FilterExec filterExec -> foundAttributes.removeAll(filterExec.condition().references()); case FilterExec filterExec -> foundAttributes.removeAll(filterExec.condition().references());
case FieldExtractExec fieldExtractExec -> {
var boundsAttributes = new HashSet<>(foundAttributes);
boundsAttributes.retainAll(fieldExtractExec.attributesToExtract());
if (boundsAttributes.isEmpty() == false) {
exec = fieldExtractExec.withBoundsAttributes(boundsAttributes);
}
}
default -> { // Do nothing default -> { // Do nothing
} }
} }
return exec; return exec;
}); });
return foundAttributes;
}
private static PhysicalPlan transformFieldExtractExec(FieldExtractExec fieldExtractExec, Set<Attribute> foundAttributes) {
var boundsAttributes = new HashSet<>(foundAttributes);
boundsAttributes.retainAll(fieldExtractExec.attributesToExtract());
return fieldExtractExec.withBoundsAttributes(boundsAttributes);
}
private static PhysicalPlan transformAggregateExec(AggregateExec agg, Set<Attribute> foundAttributes) {
return agg.transformExpressionsDown(
SpatialExtent.class,
spatialExtent -> foundAttributes.contains(spatialExtent.field())
? spatialExtent.withFieldExtractPreference(MappedFieldType.FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS)
: spatialExtent
);
} }
private static boolean isShape(DataType dataType) { private static boolean isShape(DataType dataType) {

View file

@ -180,10 +180,8 @@ final class AggregateMapper {
types = List.of("GeoPoint", "CartesianPoint"); types = List.of("GeoPoint", "CartesianPoint");
extraConfigs = SPATIAL_EXTRA_CONFIGS; extraConfigs = SPATIAL_EXTRA_CONFIGS;
} else if (clazz == SpatialExtent.class) { } else if (clazz == SpatialExtent.class) {
return Stream.concat( types = List.of("GeoPoint", "CartesianPoint", "GeoShape", "CartesianShape");
combine(clazz, List.of("GeoPoint", "CartesianPoint"), SPATIAL_EXTRA_CONFIGS), extraConfigs = SPATIAL_EXTRA_CONFIGS;
combine(clazz, List.of("GeoShape", "CartesianShape"), List.of(""))
);
} else if (Values.class.isAssignableFrom(clazz)) { } else if (Values.class.isAssignableFrom(clazz)) {
// TODO can't we figure this out from the function itself? // TODO can't we figure this out from the function itself?
types = List.of("Int", "Long", "Double", "Boolean", "BytesRef"); types = List.of("Int", "Long", "Double", "Boolean", "BytesRef");

View file

@ -59,6 +59,7 @@ import java.util.function.Function;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.DOC_VALUES; import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.DOC_VALUES;
import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS;
import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.NONE; import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.NONE;
import static org.elasticsearch.xpack.esql.core.util.Queries.Clause.FILTER; import static org.elasticsearch.xpack.esql.core.util.Queries.Clause.FILTER;
import static org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushFiltersToSource.canPushToSource; import static org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushFiltersToSource.canPushToSource;
@ -284,7 +285,7 @@ public class PlannerUtils {
case DOC_DATA_TYPE -> ElementType.DOC; case DOC_DATA_TYPE -> ElementType.DOC;
case TSID_DATA_TYPE -> ElementType.BYTES_REF; case TSID_DATA_TYPE -> ElementType.BYTES_REF;
case GEO_POINT, CARTESIAN_POINT -> fieldExtractPreference == DOC_VALUES ? ElementType.LONG : ElementType.BYTES_REF; case GEO_POINT, CARTESIAN_POINT -> fieldExtractPreference == DOC_VALUES ? ElementType.LONG : ElementType.BYTES_REF;
case GEO_SHAPE, CARTESIAN_SHAPE -> ElementType.BYTES_REF; case GEO_SHAPE, CARTESIAN_SHAPE -> fieldExtractPreference == EXTRACT_SPATIAL_BOUNDS ? ElementType.INT : ElementType.BYTES_REF;
case PARTIAL_AGG -> ElementType.COMPOSITE; case PARTIAL_AGG -> ElementType.COMPOSITE;
case SHORT, BYTE, DATE_PERIOD, TIME_DURATION, OBJECT, FLOAT, HALF_FLOAT, SCALED_FLOAT -> throw EsqlIllegalArgumentException case SHORT, BYTE, DATE_PERIOD, TIME_DURATION, OBJECT, FLOAT, HALF_FLOAT, SCALED_FLOAT -> throw EsqlIllegalArgumentException
.illegalDataType(dataType); .illegalDataType(dataType);
@ -300,11 +301,4 @@ public class PlannerUtils {
new NoopCircuitBreaker("noop-esql-breaker"), new NoopCircuitBreaker("noop-esql-breaker"),
BigArrays.NON_RECYCLING_INSTANCE BigArrays.NON_RECYCLING_INSTANCE
); );
/**
* Returns DOC_VALUES if the given boolean is set.
*/
public static MappedFieldType.FieldExtractPreference extractPreference(boolean hasPreference) {
return hasPreference ? DOC_VALUES : NONE;
}
} }

View file

@ -60,6 +60,7 @@ import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not; import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or; import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison; import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
import org.elasticsearch.xpack.esql.core.tree.Node;
import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.type.EsField;
@ -218,7 +219,10 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
private TestDataSource airportsNotIndexed; // Test when spatial field has doc values but is not indexed private TestDataSource airportsNotIndexed; // Test when spatial field has doc values but is not indexed
private TestDataSource airportsNotIndexedNorDocValues; // Test when spatial field is neither indexed nor has doc-values private TestDataSource airportsNotIndexedNorDocValues; // Test when spatial field is neither indexed nor has doc-values
private TestDataSource airportsWeb; // Cartesian point field tests private TestDataSource airportsWeb; // Cartesian point field tests
private TestDataSource airportsCityBoundaries; private TestDataSource airportsCityBoundaries; // geo_shape field tests
private TestDataSource airportsCityBoundariesNoPointDocValues; // Disable doc-values on geo_point fields, but not geo_shape fields
private TestDataSource airportsCityBoundariesNoShapeDocValues; // Disable doc-values on geo_shape fields, but not geo_point fields
private TestDataSource airportsCityBoundariesNoDocValues; // Dsiable doc-values on both geo_point and geo_shape fields
private TestDataSource cartesianMultipolygons; // cartesian_shape field tests private TestDataSource cartesianMultipolygons; // cartesian_shape field tests
private TestDataSource cartesianMultipolygonsNoDocValues; // cartesian_shape field tests but has no doc values private TestDataSource cartesianMultipolygonsNoDocValues; // cartesian_shape field tests but has no doc values
private TestDataSource countriesBbox; // geo_shape field tests private TestDataSource countriesBbox; // geo_shape field tests
@ -296,6 +300,27 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
functionRegistry, functionRegistry,
enrichResolution enrichResolution
); );
this.airportsCityBoundariesNoPointDocValues = makeTestDataSource(
"airports_city_boundaries",
"mapping-airport_city_boundaries.json",
functionRegistry,
enrichResolution,
new TestConfigurableSearchStats().exclude(Config.DOC_VALUES, "location", "city_location")
);
this.airportsCityBoundariesNoShapeDocValues = makeTestDataSource(
"airports_city_boundaries",
"mapping-airport_city_boundaries.json",
functionRegistry,
enrichResolution,
new TestConfigurableSearchStats().exclude(Config.DOC_VALUES, "city_boundary")
);
this.airportsCityBoundariesNoDocValues = makeTestDataSource(
"airports_city_boundaries",
"mapping-airport_city_boundaries.json",
functionRegistry,
enrichResolution,
new TestConfigurableSearchStats().exclude(Config.DOC_VALUES, "city_boundary", "location", "city_location")
);
this.cartesianMultipolygons = makeTestDataSource( this.cartesianMultipolygons = makeTestDataSource(
"cartesian_multipolygons", "cartesian_multipolygons",
"mapping-cartesian_multipolygons.json", "mapping-cartesian_multipolygons.json",
@ -3274,10 +3299,10 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
* ][_doc{f}#36], limit[], sort[] estimatedRowSize[204] * ][_doc{f}#36], limit[], sort[] estimatedRowSize[204]
* </code> * </code>
*/ */
public void testSpatialTypesAndStatsExtentOfGeoShapeDoesNotUseBinaryExtraction() { public void testSpatialTypesAndStatsExtentOfGeoShapeUsesBinaryExtraction() {
// TODO: When we get geo_shape working with bounds extraction from doc-values, change the name of this test
var query = "FROM airports_city_boundaries | STATS extent = ST_EXTENT_AGG(city_boundary)"; var query = "FROM airports_city_boundaries | STATS extent = ST_EXTENT_AGG(city_boundary)";
var testData = airportsCityBoundaries; for (boolean useDocValues : new Boolean[] { true, false }) {
var testData = useDocValues ? airportsCityBoundaries : airportsCityBoundariesNoDocValues;
var plan = physicalPlan(query, testData); var plan = physicalPlan(query, testData);
var limit = as(plan, LimitExec.class); var limit = as(plan, LimitExec.class);
@ -3298,15 +3323,15 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
exchange = as(agg.child(), ExchangeExec.class); exchange = as(agg.child(), ExchangeExec.class);
agg = as(exchange.child(), AggregateExec.class); agg = as(exchange.child(), AggregateExec.class);
// below the exchange (in data node) the aggregation is using a specific // below the exchange (in data node) the aggregation is using a specific int[] which the aggregation needs to know about.
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); var fieldExtractPreference = useDocValues ? FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS : FieldExtractPreference.NONE;
assertChildIsExtractedAs(agg, FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS, GEO_SHAPE); assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, fieldExtractPreference);
assertChildIsExtractedAs(agg, fieldExtractPreference, GEO_SHAPE);
}
} }
/** /**
* This test verifies that the aggregation does not use spatial bounds extraction when the shape appears in an eval or filter. * This test verifies that the aggregation does not use spatial bounds extraction when the shape appears in an eval or filter.
* TODO: Currently this tests nothing, because geo_shape is not supported anyway for bounds extraction,
* but it should be updated when it is supported.
*/ */
public void testSpatialTypesAndStatsExtentOfShapesNegativeCases() { public void testSpatialTypesAndStatsExtentOfShapesNegativeCases() {
for (String query : new String[] { """ for (String query : new String[] { """
@ -3329,6 +3354,7 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
var exchange = as(agg.child(), ExchangeExec.class); var exchange = as(agg.child(), ExchangeExec.class);
agg = as(exchange.child(), AggregateExec.class); agg = as(exchange.child(), AggregateExec.class);
// Because the shape was used in EVAL/WHERE we cannot use doc-values bounds extraction optimization
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
var exec = agg.child() instanceof FieldExtractExec ? agg : as(agg.child(), UnaryExec.class); var exec = agg.child() instanceof FieldExtractExec ? agg : as(agg.child(), UnaryExec.class);
assertChildIsExtractedAs(exec, FieldExtractPreference.NONE, GEO_SHAPE); assertChildIsExtractedAs(exec, FieldExtractPreference.NONE, GEO_SHAPE);
@ -3354,19 +3380,11 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
var optimized = optimizedPlan(plan, testData.stats); var optimized = optimizedPlan(plan, testData.stats);
limit = as(optimized, LimitExec.class); limit = as(optimized, LimitExec.class);
agg = as(limit.child(), AggregateExec.class); agg = as(limit.child(), AggregateExec.class);
// For cartesian_shape extraction, we extract bounds from doc-values directly into a BBOX encoded as BytesRef,
// so the aggregation does not need to know about it.
assertAggregation(agg, "extent", SpatialExtent.class, CARTESIAN_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", SpatialExtent.class, CARTESIAN_SHAPE, FieldExtractPreference.NONE);
var exchange = as(agg.child(), ExchangeExec.class); var exchange = as(agg.child(), ExchangeExec.class);
agg = as(exchange.child(), AggregateExec.class); agg = as(exchange.child(), AggregateExec.class);
assertAggregation( // We extract bounds from doc-values into a special int[] which the aggregation needs to know about.
agg, assertAggregation(agg, "extent", "hasDocValues:" + hasDocValues, SpatialExtent.class, CARTESIAN_SHAPE, fieldExtractPreference);
"extent",
"hasDocValues:" + hasDocValues,
SpatialExtent.class,
CARTESIAN_SHAPE,
FieldExtractPreference.NONE
);
var exec = agg.child() instanceof FieldExtractExec ? agg : as(agg.child(), UnaryExec.class); var exec = agg.child() instanceof FieldExtractExec ? agg : as(agg.child(), UnaryExec.class);
// For cartesian_shape, the bounds extraction is done in the FieldExtractExec, so it does need to know about this // For cartesian_shape, the bounds extraction is done in the FieldExtractExec, so it does need to know about this
assertChildIsExtractedAs(exec, fieldExtractPreference, CARTESIAN_SHAPE); assertChildIsExtractedAs(exec, fieldExtractPreference, CARTESIAN_SHAPE);
@ -3374,40 +3392,47 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
} }
/** /**
* Before local optimizations: * This tests all four combinations of geo_point and geo_shape with and without doc-values.
* Since each will be extracted differently (points as encoded longs, and shapes as int[5] bounds representing Extents),
* we want to verify that the combinations do not clash and work together.
* The optimized query plan in the case when both points and shapes have doc-values will look like:
* <code> * <code>
* LimitExec[1000[INTEGER]] * LimitExec[1000[INTEGER]]
* \_AggregateExec[[],[SPATIALEXTENT(city_boundary{f}#13,true[BOOLEAN]) AS extent, SPATIALCENTROID(city_location{f}#12,true[BOOLEA * \_AggregateExec[[],[
* N]) AS centroid],...] * SPATIALEXTENT(city_boundary{f}#13,true[BOOLEAN]) AS extent,
* \_ExchangeExec[[..]] * SPATIALCENTROID(city_location{f}#12,true[BOOLEAN]) AS centroid
* \_FragmentExec[filter=null, estimatedRowSize=0, reducer=[], fragment=[...]] * ],FINAL,[...bounds attributes..., ...centroid attributes...],221]
* \_EsRelation[airports_city_boundaries][abbrev{f}#8, airport{f}#9, city{f}#11, city_boundar..] * \_ExchangeExec[[...bounds attributes..., ...centroid attributes...],true]
* </code> * \_AggregateExec[[],[
* After local optimizations: * SPATIALEXTENT(city_boundary{f}#13,true[BOOLEAN]) AS extent,
* <code> * SPATIALCENTROID(city_location{f}#12,true[BOOLEAN]) AS centroid
* LimitExec[1000[INTEGER]] * ],INITIAL,[...bounds attributes..., ...centroid attributes...],221]
* \_AggregateExec[[],[SPATIALSTEXTENT(location{f}#48,true[BOOLEAN]) AS extent],FINAL,[minNegX{r}#52, minPosX{r}#53, maxNegX{r}#54, * \_FieldExtractExec[city_boundary{f}#13, city_location{f}#12][city_location{f}#12],[city_boundary{f}#13]
* maxPosX{r}#55, maxY{r}#56, minY{r}#57],21] * \_EsQueryExec[airports_city_boundaries], indexMode[standard], query[
* \_ExchangeExec[[minNegX{r}#52, minPosX{r}#53, maxNegX{r}#54, maxPosX{r}#55, maxY{r}#56, minY{r}#57],true] * {"bool":{"should":[
* \_AggregateExec[[],[SPATIALSTEXTENT(location{f}#48,true[BOOLEAN]) AS extent],INITIAL,[ * {"exists":{"field":"city_boundary","boost":1.0}},
* minNegX{r}#73, minPosX{r}#74, maxNegX{rb#75, maxPosX{r}#76, maxY{r}#77, minY{r}#78],21] * {"exists":{"field":"city_location","boost":1.0}}
* \_FieldExtractExec[location{f}#48][location{f}#48] * ],"boost":1.0}}
* \_EsQueryExec[airports], indexMode[standard], query[{"exists":{"field":"location","boost":1.0}}][ * ][_doc{f}#55], limit[], sort[] estimatedRowSize[225]
* _doc{f}#79], limit[], sort[] estimatedRowSize[25]
* </code> * </code>
*/ */
public void testMixedSpatialBoundsAndPointsExtracted() { public void testMixedSpatialBoundsAndPointsExtracted() {
var query = """ var query = """
FROM airports_city_boundaries \ FROM airports_city_boundaries \
| STATS extent = ST_EXTENT_AGG(city_boundary), centroid = ST_CENTROID_AGG(city_location)"""; | STATS extent = ST_EXTENT_AGG(city_boundary), centroid = ST_CENTROID_AGG(city_location)""";
var testData = airportsCityBoundaries; for (boolean pointDocValues : new Boolean[] { true, false }) {
for (boolean shapeDocValues : new Boolean[] { true, false }) {
var testData = pointDocValues
? (shapeDocValues ? airportsCityBoundaries : airportsCityBoundariesNoShapeDocValues)
: (shapeDocValues ? airportsCityBoundariesNoPointDocValues : airportsCityBoundariesNoDocValues);
var msg = "DocValues[point:" + pointDocValues + ", shape:" + shapeDocValues + "]";
var plan = physicalPlan(query, testData); var plan = physicalPlan(query, testData);
var limit = as(plan, LimitExec.class); var limit = as(plan, LimitExec.class);
var agg = as(limit.child(), AggregateExec.class); var agg = as(limit.child(), AggregateExec.class);
// Before optimization the aggregation does not use doc-values // Before optimization the aggregation does not use doc-values
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", msg, SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
assertAggregation(agg, "centroid", SpatialCentroid.class, GEO_POINT, FieldExtractPreference.NONE); assertAggregation(agg, "centroid", msg, SpatialCentroid.class, GEO_POINT, FieldExtractPreference.NONE);
var exchange = as(agg.child(), ExchangeExec.class); var exchange = as(agg.child(), ExchangeExec.class);
var fragment = as(exchange.child(), FragmentExec.class); var fragment = as(exchange.child(), FragmentExec.class);
@ -3419,15 +3444,20 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
limit = as(optimized, LimitExec.class); limit = as(optimized, LimitExec.class);
agg = as(limit.child(), AggregateExec.class); agg = as(limit.child(), AggregateExec.class);
// Above the exchange (in coordinator) the aggregation is not field-optimized. // Above the exchange (in coordinator) the aggregation is not field-optimized.
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE); assertAggregation(agg, "extent", msg, SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
assertAggregation(agg, "centroid", SpatialCentroid.class, GEO_POINT, FieldExtractPreference.NONE); assertAggregation(agg, "centroid", msg, SpatialCentroid.class, GEO_POINT, FieldExtractPreference.NONE);
exchange = as(agg.child(), ExchangeExec.class); exchange = as(agg.child(), ExchangeExec.class);
agg = as(exchange.child(), AggregateExec.class); agg = as(exchange.child(), AggregateExec.class);
// below the exchange (in data node) the aggregation is field optimized.
assertAggregation(agg, "extent", SpatialExtent.class, GEO_SHAPE, FieldExtractPreference.NONE);
var fieldExtractExec = as(agg.child(), FieldExtractExec.class); var fieldExtractExec = as(agg.child(), FieldExtractExec.class);
assertThat(fieldExtractExec.boundsAttributes().stream().map(a -> a.sourceText()).toList(), equalTo(List.of("city_boundary"))); // below the exchange (in data node) the aggregation is field optimized.
assertThat(fieldExtractExec.docValuesAttributes().stream().map(a -> a.sourceText()).toList(), equalTo(List.of("city_location"))); var shapeExtractPreference = shapeDocValues ? FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS : FieldExtractPreference.NONE;
assertAggregation(agg, "extent", msg, SpatialExtent.class, GEO_SHAPE, shapeExtractPreference);
List<String> boundsAttributes = shapeDocValues ? List.of("city_boundary") : List.of();
List<String> docValuesAttributes = pointDocValues ? List.of("city_location") : List.of();
assertThat(fieldExtractExec.boundsAttributes().stream().map(Node::sourceText).toList(), equalTo(boundsAttributes));
assertThat(fieldExtractExec.docValuesAttributes().stream().map(Node::sourceText).toList(), equalTo(docValuesAttributes));
}
}
} }
/** /**
@ -7746,7 +7776,7 @@ public class PhysicalPlanOptimizerTests extends ESTestCase {
var aggFunc = assertAggregation(plan, aliasName, aggClass); var aggFunc = assertAggregation(plan, aliasName, aggClass);
var aggField = as(aggFunc.field(), Attribute.class); var aggField = as(aggFunc.field(), Attribute.class);
var spatialAgg = as(aggFunc, SpatialAggregateFunction.class); var spatialAgg = as(aggFunc, SpatialAggregateFunction.class);
assertThat(spatialAgg.fieldExtractPreference(), equalTo(fieldExtractPreference)); assertThat(reason, spatialAgg.fieldExtractPreference(), equalTo(fieldExtractPreference));
assertThat(reason, aggField.dataType(), equalTo(fieldType)); assertThat(reason, aggField.dataType(), equalTo(fieldType));
} }

View file

@ -21,6 +21,7 @@ import org.elasticsearch.compute.data.BytesRefBlock;
import org.elasticsearch.compute.data.DocBlock; import org.elasticsearch.compute.data.DocBlock;
import org.elasticsearch.compute.data.DocVector; import org.elasticsearch.compute.data.DocVector;
import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.ElementType;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.IntVector;
import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongBlock;
import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.data.Page;
@ -33,9 +34,14 @@ import org.elasticsearch.compute.operator.SourceOperator.SourceOperatorFactory;
import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Nullable;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.GeometryValidator;
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
import org.elasticsearch.geometry.utils.WellKnownBinary;
import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.analysis.AnalysisRegistry;
import org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference; import org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference;
import org.elasticsearch.indices.analysis.AnalysisModule; import org.elasticsearch.indices.analysis.AnalysisModule;
import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.plugins.scanners.StablePluginsRegistry; import org.elasticsearch.plugins.scanners.StablePluginsRegistry;
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
import org.elasticsearch.xpack.esql.TestBlockFactory; import org.elasticsearch.xpack.esql.TestBlockFactory;
@ -68,6 +74,9 @@ import java.util.stream.IntStream;
import static com.carrotsearch.randomizedtesting.generators.RandomNumbers.randomIntBetween; import static com.carrotsearch.randomizedtesting.generators.RandomNumbers.randomIntBetween;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
import static org.apache.lucene.tests.util.LuceneTestCase.createTempDir; import static org.apache.lucene.tests.util.LuceneTestCase.createTempDir;
import static org.elasticsearch.compute.aggregation.spatial.SpatialAggregationUtils.encodeLongitude;
import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.DOC_VALUES;
import static org.elasticsearch.index.mapper.MappedFieldType.FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS;
public class TestPhysicalOperationProviders extends AbstractPhysicalOperationProviders { public class TestPhysicalOperationProviders extends AbstractPhysicalOperationProviders {
private final List<IndexPage> indexPages; private final List<IndexPage> indexPages;
@ -103,13 +112,7 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
PhysicalOperation op = source; PhysicalOperation op = source;
for (Attribute attr : fieldExtractExec.attributesToExtract()) { for (Attribute attr : fieldExtractExec.attributesToExtract()) {
layout.append(attr); layout.append(attr);
op = op.with( op = op.with(new TestFieldExtractOperatorFactory(attr, fieldExtractExec.fieldExtractPreference(attr)), layout.build());
new TestFieldExtractOperatorFactory(
attr,
PlannerUtils.extractPreference(fieldExtractExec.docValuesAttributes().contains(attr))
),
layout.build()
);
} }
return op; return op;
} }
@ -397,17 +400,16 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
FieldExtractPreference extractPreference, FieldExtractPreference extractPreference,
BiFunction<DocBlock, TestBlockCopier, Block> extractBlock BiFunction<DocBlock, TestBlockCopier, Block> extractBlock
) { ) {
BlockFactory blockFactory = docBlock.blockFactory();
boolean mapToDocValues = shouldMapToDocValues(dataType, extractPreference);
try ( try (
Block.Builder blockBuilder = mapToDocValues Block.Builder blockBuilder = blockBuilder(
? blockFactory.newLongBlockBuilder(docBlock.getPositionCount()) dataType,
: blockBuilder(dataType, docBlock.getPositionCount(), TestBlockFactory.getNonBreakingInstance()) extractPreference,
docBlock.getPositionCount(),
TestBlockFactory.getNonBreakingInstance()
)
) { ) {
foreachIndexDoc(docBlock, indexDoc -> { foreachIndexDoc(docBlock, indexDoc -> {
TestBlockCopier blockCopier = mapToDocValues TestBlockCopier blockCopier = blockCopier(dataType, extractPreference, indexDoc.asVector().docs());
? TestSpatialPointStatsBlockCopier.create(indexDoc.asVector().docs(), dataType)
: new TestBlockCopier(indexDoc.asVector().docs());
Block blockForIndex = extractBlock.apply(indexDoc, blockCopier); Block blockForIndex = extractBlock.apply(indexDoc, blockCopier);
blockBuilder.copyFrom(blockForIndex, 0, blockForIndex.getPositionCount()); blockBuilder.copyFrom(blockForIndex, 0, blockForIndex.getPositionCount());
}); });
@ -418,10 +420,6 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
} }
} }
private boolean shouldMapToDocValues(DataType dataType, FieldExtractPreference extractPreference) {
return extractPreference == FieldExtractPreference.DOC_VALUES && DataType.isSpatialPoint(dataType);
}
private static class TestBlockCopier { private static class TestBlockCopier {
protected final IntVector docIndices; protected final IntVector docIndices;
@ -447,7 +445,6 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
/** /**
* geo_point and cartesian_point are normally loaded as WKT from source, but for aggregations we can load them as doc-values * geo_point and cartesian_point are normally loaded as WKT from source, but for aggregations we can load them as doc-values
* which are encoded Long values. This class is used to convert the test loaded WKB into encoded longs for the aggregators. * which are encoded Long values. This class is used to convert the test loaded WKB into encoded longs for the aggregators.
* TODO: We need a different solution to support geo_shape and cartesian_shape
*/ */
private abstract static class TestSpatialPointStatsBlockCopier extends TestBlockCopier { private abstract static class TestSpatialPointStatsBlockCopier extends TestBlockCopier {
@ -465,15 +462,15 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
for (int c = 0; c < docIndices.getPositionCount(); c++) { for (int c = 0; c < docIndices.getPositionCount(); c++) {
int doc = docIndices.getInt(c); int doc = docIndices.getInt(c);
int count = bytesRefBlock.getValueCount(doc); int count = bytesRefBlock.getValueCount(doc);
int i = bytesRefBlock.getFirstValueIndex(doc);
if (count == 0) { if (count == 0) {
builder.appendNull(); builder.appendNull();
} else { } else {
if (count > 1) { if (count > 1) {
builder.beginPositionEntry(); builder.beginPositionEntry();
} }
for (int v = 0; v < count; v++) { int firstValueIndex = bytesRefBlock.getFirstValueIndex(doc);
builder.appendLong(encode(bytesRefBlock.getBytesRef(i + v, scratch))); for (int i = firstValueIndex; i < firstValueIndex + count; i++) {
builder.appendLong(encode(bytesRefBlock.getBytesRef(i, scratch)));
} }
if (count > 1) { if (count > 1) {
builder.endPositionEntry(); builder.endPositionEntry();
@ -499,12 +496,123 @@ public class TestPhysicalOperationProviders extends AbstractPhysicalOperationPro
} }
} }
private static Block.Builder blockBuilder(DataType dataType, int estimatedSize, BlockFactory blockFactory) { /**
* geo_shape and cartesian_shape are normally loaded as WKT from source, but for ST_EXTENT_AGG we can load them from doc-values
* extracting the spatial Extent information. This class is used to convert the test loaded WKB into the int[6] used in the aggregators.
*/
private abstract static class TestSpatialShapeExtentBlockCopier extends TestBlockCopier {
protected final SpatialEnvelopeVisitor.PointVisitor pointVisitor;
private final SpatialEnvelopeVisitor visitor;
private TestSpatialShapeExtentBlockCopier(IntVector docIndices, SpatialEnvelopeVisitor.PointVisitor pointVisitor) {
super(docIndices);
this.pointVisitor = pointVisitor;
this.visitor = new SpatialEnvelopeVisitor(pointVisitor);
}
@Override
protected Block copyBlock(Block originalData) {
BytesRef scratch = new BytesRef(100);
BytesRefBlock bytesRefBlock = (BytesRefBlock) originalData;
try (IntBlock.Builder builder = bytesRefBlock.blockFactory().newIntBlockBuilder(docIndices.getPositionCount())) {
for (int c = 0; c < docIndices.getPositionCount(); c++) {
int doc = docIndices.getInt(c);
int count = bytesRefBlock.getValueCount(doc);
if (count == 0) {
builder.appendNull();
} else {
pointVisitor.reset();
int firstValueIndex = bytesRefBlock.getFirstValueIndex(doc);
for (int i = firstValueIndex; i < firstValueIndex + count; i++) {
BytesRef wkb = bytesRefBlock.getBytesRef(i, scratch);
Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, false, wkb.bytes, wkb.offset, wkb.length);
geometry.visit(visitor);
}
encodeExtent(builder);
}
}
return builder.build();
}
}
protected abstract void encodeExtent(IntBlock.Builder builder);
private static TestSpatialShapeExtentBlockCopier create(IntVector docIndices, DataType dataType) {
return switch (dataType) {
case GEO_SHAPE -> new TestGeoCopier(docIndices);
case CARTESIAN_SHAPE -> new TestCartesianCopier(docIndices);
default -> throw new IllegalArgumentException("Unsupported spatial data type: " + dataType);
};
}
private static class TestGeoCopier extends TestSpatialShapeExtentBlockCopier {
private TestGeoCopier(IntVector docIndices) {
super(docIndices, new SpatialEnvelopeVisitor.GeoPointVisitor(SpatialEnvelopeVisitor.WrapLongitude.WRAP));
}
@Override
protected void encodeExtent(IntBlock.Builder builder) {
// We store the 6 values as a single multi-valued field, in the same order as the fields in the Extent class
// This requires that consumers also know the meaning of the values, which they can learn from the Extent class
SpatialEnvelopeVisitor.GeoPointVisitor visitor = (SpatialEnvelopeVisitor.GeoPointVisitor) pointVisitor;
builder.beginPositionEntry();
builder.appendInt(CoordinateEncoder.GEO.encodeY(visitor.getTop()));
builder.appendInt(CoordinateEncoder.GEO.encodeY(visitor.getBottom()));
builder.appendInt(encodeLongitude(visitor.getNegLeft()));
builder.appendInt(encodeLongitude(visitor.getNegRight()));
builder.appendInt(encodeLongitude(visitor.getPosLeft()));
builder.appendInt(encodeLongitude(visitor.getPosRight()));
builder.endPositionEntry();
}
}
private static class TestCartesianCopier extends TestSpatialShapeExtentBlockCopier {
private TestCartesianCopier(IntVector docIndices) {
super(docIndices, new SpatialEnvelopeVisitor.CartesianPointVisitor());
}
@Override
protected void encodeExtent(IntBlock.Builder builder) {
// We store the 4 values as a single multi-valued field, in the same order as the fields in the Rectangle class
// This requires that consumers also know the meaning of the values, which they can learn from the Rectangle class
SpatialEnvelopeVisitor.CartesianPointVisitor visitor = (SpatialEnvelopeVisitor.CartesianPointVisitor) pointVisitor;
builder.beginPositionEntry();
builder.appendInt(CoordinateEncoder.CARTESIAN.encodeX(visitor.getMinX()));
builder.appendInt(CoordinateEncoder.CARTESIAN.encodeX(visitor.getMaxX()));
builder.appendInt(CoordinateEncoder.CARTESIAN.encodeY(visitor.getMaxY()));
builder.appendInt(CoordinateEncoder.CARTESIAN.encodeY(visitor.getMinY()));
builder.endPositionEntry();
}
}
}
private static Block.Builder blockBuilder(
DataType dataType,
FieldExtractPreference extractPreference,
int estimatedSize,
BlockFactory blockFactory
) {
ElementType elementType = switch (dataType) { ElementType elementType = switch (dataType) {
case SHORT -> ElementType.INT; case SHORT -> ElementType.INT;
case FLOAT, HALF_FLOAT, SCALED_FLOAT -> ElementType.DOUBLE; case FLOAT, HALF_FLOAT, SCALED_FLOAT -> ElementType.DOUBLE;
default -> PlannerUtils.toElementType(dataType); default -> PlannerUtils.toElementType(dataType);
}; };
if (extractPreference == DOC_VALUES && DataType.isSpatialPoint(dataType)) {
return blockFactory.newLongBlockBuilder(estimatedSize);
} else if (extractPreference == EXTRACT_SPATIAL_BOUNDS && DataType.isSpatial(dataType)) {
return blockFactory.newIntBlockBuilder(estimatedSize);
} else {
return elementType.newBlockBuilder(estimatedSize, blockFactory); return elementType.newBlockBuilder(estimatedSize, blockFactory);
} }
}
private static TestBlockCopier blockCopier(DataType dataType, FieldExtractPreference extractPreference, IntVector docIndices) {
if (extractPreference == DOC_VALUES && DataType.isSpatialPoint(dataType)) {
return TestSpatialPointStatsBlockCopier.create(docIndices, dataType);
} else if (extractPreference == EXTRACT_SPATIAL_BOUNDS && DataType.isSpatial(dataType)) {
return TestSpatialShapeExtentBlockCopier.create(docIndices, dataType);
} else {
return new TestBlockCopier(docIndices);
}
}
} }

View file

@ -33,6 +33,7 @@ import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.BlockLoader;
import org.elasticsearch.index.mapper.DocumentParserContext; import org.elasticsearch.index.mapper.DocumentParserContext;
import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.index.mapper.GeoShapeIndexer;
@ -300,14 +301,17 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
} }
@Override @Override
protected boolean isBoundsExtractionSupported() { public BlockLoader blockLoader(BlockLoaderContext blContext) {
// Extracting bounds for geo shapes is not implemented yet. return blContext.fieldExtractPreference() == FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS
return false; ? new GeoBoundsBlockLoader(name())
: blockLoaderFromSource(blContext);
} }
@Override static class GeoBoundsBlockLoader extends AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType.BoundsBlockLoader {
protected CoordinateEncoder coordinateEncoder() {
return CoordinateEncoder.GEO; GeoBoundsBlockLoader(String fieldName) {
super(fieldName);
}
} }
} }

View file

@ -22,6 +22,7 @@ import org.elasticsearch.index.fielddata.FieldDataContext;
import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.fielddata.ScriptDocValues;
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
import org.elasticsearch.index.mapper.BlockLoader;
import org.elasticsearch.index.mapper.DocumentParserContext; import org.elasticsearch.index.mapper.DocumentParserContext;
import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
@ -31,6 +32,7 @@ import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.lucene.spatial.BinaryShapeDocValuesField; import org.elasticsearch.lucene.spatial.BinaryShapeDocValuesField;
import org.elasticsearch.lucene.spatial.CartesianShapeIndexer; import org.elasticsearch.lucene.spatial.CartesianShapeIndexer;
import org.elasticsearch.lucene.spatial.CoordinateEncoder; import org.elasticsearch.lucene.spatial.CoordinateEncoder;
import org.elasticsearch.lucene.spatial.Extent;
import org.elasticsearch.lucene.spatial.XYQueriesUtils; import org.elasticsearch.lucene.spatial.XYQueriesUtils;
import org.elasticsearch.script.field.AbstractScriptFieldFactory; import org.elasticsearch.script.field.AbstractScriptFieldFactory;
import org.elasticsearch.script.field.DocValuesScriptFieldFactory; import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
@ -186,13 +188,26 @@ public class ShapeFieldMapper extends AbstractShapeGeometryFieldMapper<Geometry>
} }
@Override @Override
protected boolean isBoundsExtractionSupported() { public BlockLoader blockLoader(BlockLoaderContext blContext) {
return true; return blContext.fieldExtractPreference() == FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS
? new CartesianBoundsBlockLoader(name())
: blockLoaderFromSource(blContext);
} }
@Override static class CartesianBoundsBlockLoader extends BoundsBlockLoader {
protected CoordinateEncoder coordinateEncoder() { protected CartesianBoundsBlockLoader(String fieldName) {
return CoordinateEncoder.CARTESIAN; super(fieldName);
}
protected void writeExtent(BlockLoader.IntBuilder builder, Extent extent) {
// For cartesian_shape we store 4 values as a multi-valued field, in the same order as the fields in the Rectangle class
builder.beginPositionEntry();
builder.appendInt(Math.min(extent.negLeft, extent.posLeft));
builder.appendInt(Math.max(extent.negRight, extent.posRight));
builder.appendInt(extent.top);
builder.appendInt(extent.bottom);
builder.endPositionEntry();
}
} }
} }