/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.qp.storeadapter.indexcursor; import com.foundationdb.ais.model.Index; import com.foundationdb.server.types.value.ValueRecord; import com.foundationdb.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.BindingsAwareCursor; import com.foundationdb.qp.operator.QueryBindings; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.InternalIndexTypes; import com.foundationdb.server.api.dml.IndexRowPrefixSelector; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.common.types.TBigDecimal; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.texpressions.TPreparedExpression; import com.foundationdb.server.spatial.Spatial; import com.geophile.z.Space; import java.math.BigDecimal; import static java.lang.Math.abs; // An IndexCursorSpatial_NearPoint yields points near a given point in z-order. This requires a merge // of those with a lower z-value and those with a higher z-value. class IndexCursorSpatial_NearPoint extends IndexCursor { @Override public void open() { super.open(); // Close any store data still open. iterationHelper.closeIteration(); geCursor.open(); geNeedToAdvance = true; ltCursor.open(); ltNeedToAdvance = true; } @Override public Row next() { Row next; super.next(); if (geNeedToAdvance) { geRow = geCursor.next(); geDistance = distanceFromStart(geRow); geNeedToAdvance = false; } if (ltNeedToAdvance) { ltRow = ltCursor.next(); ltDistance = distanceFromStart(ltRow); ltNeedToAdvance = false; } if (ltDistance < geDistance) { next = ltRow; ltNeedToAdvance = true; } else { next = geRow; geNeedToAdvance = true; } if (next == null) { setIdle(); } return next; } @Override public void close() { if (!isClosed()) { super.close(); geCursor.close(); ltCursor.close(); } } @Override public void rebind(QueryBindings bindings) { super.rebind(bindings); geCursor.rebind(bindings); ltCursor.rebind(bindings); } // IndexCursorSpatial_InBox interface public static IndexCursorSpatial_NearPoint create(QueryContext context, IterationHelper iterationHelper, IndexKeyRange keyRange) { return new IndexCursorSpatial_NearPoint(context, iterationHelper, keyRange); } // For use by this class private IndexCursorSpatial_NearPoint(QueryContext context, IterationHelper iterationHelper, IndexKeyRange keyRange) { super(context, iterationHelper); assert keyRange.spatialCoordsIndex(); this.iterationHelper = iterationHelper; IndexRowType physicalIndexRowType = keyRange.indexRowType().physicalRowType(); Index index = keyRange.indexRowType().index(); Space space = index.space(); int latColumn = index.firstSpatialArgument(); int lonColumn = latColumn + 1; // The index column selector needs to select all the columns before the z column, and the z column itself. IndexRowPrefixSelector indexColumnSelector = new IndexRowPrefixSelector(latColumn + 1); IndexBound loBound = keyRange.lo(); ValueRecord loExpressions = loBound.boundExpressions(context, bindings); // Compute z-value at beginning of forward and backward scans TInstance latInstance = index.getAllColumns().get(latColumn).getColumn().getType(); TInstance lonInstance = index.getAllColumns().get(lonColumn).getColumn().getType(); BigDecimal lat = TBigDecimal.getWrapper(loExpressions.value(latColumn), latInstance).asBigDecimal(); BigDecimal lon = TBigDecimal.getWrapper(loExpressions.value(lonColumn), lonInstance).asBigDecimal(); zStart = Spatial.shuffle(space, lat.doubleValue(), lon.doubleValue()); // Cursors going forward from starting z value (inclusive), and backward from the same z value (exclusive) int indexRowFields = physicalIndexRowType.nFields(); SpatialIndexValueRecord zForwardRow = new SpatialIndexValueRecord(indexRowFields); SpatialIndexValueRecord zBackwardRow = new SpatialIndexValueRecord(indexRowFields); SpatialIndexValueRecord zMaxRow = new SpatialIndexValueRecord(indexRowFields); SpatialIndexValueRecord zMinRow = new SpatialIndexValueRecord(indexRowFields); IndexBound zForward = new IndexBound(zForwardRow, indexColumnSelector); IndexBound zBackward = new IndexBound(zBackwardRow, indexColumnSelector); IndexBound zMax = new IndexBound(zMaxRow, indexColumnSelector); IndexBound zMin = new IndexBound(zMinRow, indexColumnSelector); // Take care of any equality restrictions before the spatial fields zPosition = latColumn; for (int f = 0; f < zPosition; f++) { ValueSource eqValueSource = loExpressions.value(f); zForwardRow.value(f, eqValueSource); zBackwardRow.value(f, eqValueSource); zMaxRow.value(f, eqValueSource); zMinRow.value(f, eqValueSource); } // Z-value part of bounds Value startValue = new Value(InternalIndexTypes.LONG.instance(false)); Value maxValue = new Value(InternalIndexTypes.LONG.instance(false)); Value minValue = new Value(InternalIndexTypes.LONG.instance(false)); startValue.putInt64(zStart); maxValue.putInt64(Long.MAX_VALUE); minValue.putInt64(Long.MIN_VALUE); zForwardRow.value(zPosition, startValue); zBackwardRow.value(zPosition, startValue); zMaxRow.value(zPosition, maxValue); zMinRow.value(zPosition, minValue); IndexKeyRange geKeyRange = IndexKeyRange.bounded(physicalIndexRowType, zForward, true, zMax, false); IndexKeyRange ltKeyRange = IndexKeyRange.bounded(physicalIndexRowType, zMin, false, zBackward, false); IterationHelper geRowState = adapter.createIterationHelper(keyRange.indexRowType()); IterationHelper ltRowState = adapter.createIterationHelper(keyRange.indexRowType()); API.Ordering upOrdering = new API.Ordering(); API.Ordering downOrdering = new API.Ordering(); for (int f = 0; f < physicalIndexRowType.nFields(); f++) { // TODO: This seems like an API hack upOrdering.append((TPreparedExpression)null, true); downOrdering.append((TPreparedExpression)null, false); } geCursor = new IndexCursorUnidirectional<>(context, geRowState, geKeyRange, upOrdering, ValueSortKeyAdapter.INSTANCE); ltCursor = new IndexCursorUnidirectional<>(context, ltRowState, ltKeyRange, downOrdering, ValueSortKeyAdapter.INSTANCE); } // For use by this class private long distanceFromStart(Row row) { long distance; if (row == null) { distance = Long.MAX_VALUE; } else { long z = row.value(zPosition).getInt64(); distance = abs(z - zStart); } return distance; } // Object state // Iteration control is slightly convoluted. lt/geNeedToAdvance are set to indicate if the lt/geCursor // need to be advanced on the *next* iteration. This simplifies row management. It would be simpler to advance // in next() after determining the next row. But then the state carrying next gets advanced and next doesn't // have the state that it should. private final int zPosition; private final IterationHelper iterationHelper; private long zStart; private BindingsAwareCursor geCursor; private Row geRow; private long geDistance; private boolean geNeedToAdvance; private BindingsAwareCursor ltCursor; private Row ltRow; private long ltDistance; private boolean ltNeedToAdvance; }