/** * 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.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.QueryContext; import com.foundationdb.qp.row.IndexRow; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.InternalIndexTypes; import com.foundationdb.server.api.dml.ColumnSelector; import com.foundationdb.server.api.dml.IndexRowPrefixSelector; import com.foundationdb.server.spatial.GeophileCursor; import com.foundationdb.server.spatial.GeophileIndex; import com.foundationdb.server.spatial.TransformingIterator; import com.foundationdb.server.types.texpressions.TPreparedField; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueRecord; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.util.IteratorToCursorAdapter; import com.geophile.z.Cursor; import com.geophile.z.Pair; import com.geophile.z.Record; import com.geophile.z.Space; import com.geophile.z.SpatialIndex; import com.geophile.z.SpatialJoin; import com.geophile.z.SpatialObject; import com.geophile.z.index.RecordWithSpatialObject; import com.geophile.z.index.sortedarray.SortedArray; import com.geophile.z.space.SpaceImpl; import com.geophile.z.spatialobject.jts.JTS; import com.vividsolutions.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; // A scan of an IndexCursorSpatial_InBox will be implemented as one or more IndexCursorUnidirectional scans. public class IndexCursorSpatial_InBox extends IndexCursor { @Override public void open() { super.open(); // Close any underlying store data still open iterationHelper.closeIteration(); createSpatialJoinCursor(); geophileCursor.rebind(bindings); spatialJoinCursor.open(); } @Override public Row next() { super.next(); return spatialJoinCursor.next(); } @Override public void close() { super.close(); spatialJoinCursor.close(); } // IndexCursorSpatial_InBox interface public static IndexCursorSpatial_InBox create(QueryContext context, IterationHelper iterationHelper, IndexKeyRange keyRange, boolean openAll) { return new IndexCursorSpatial_InBox(context, iterationHelper, keyRange, openAll); } // For use by this class private IndexCursorSpatial_InBox(final QueryContext context, IterationHelper iterationHelper, final IndexKeyRange keyRange, final boolean openEarly) { super(context, iterationHelper); this.keyRange = keyRange; this.index = keyRange.indexRowType().index(); assert index.isSpatial() : index; this.space = this.index.space(); this.openEarly = openEarly; } private void createSpatialJoinCursor() { this.loExpressions = keyRange.lo().boundExpressions(context, bindings); final SpatialObject spatialObject = spatialObject(); // Set up spatial join and iterator over spatial join output SpatialJoin spatialJoin = SpatialJoin.newSpatialJoin(SPATIAL_JOIN_DUPLICATION, null, SPATIAL_JOIN_LEFT_OBSERVER, LOG.isDebugEnabled() ? SPATIAL_JOIN_RIGHT_DEBUG_OBSERVER : SPATIAL_JOIN_RIGHT_OBSERVER); Iterator<Pair<RecordWithSpatialObject, IndexRow>> spatialJoinIterator; try { // Set up spatial index over the index SpatialIndex<IndexRow> dataSpatialIndex = SpatialIndex.newSpatialIndex(space, dataIndex(context, openEarly, spatialObject)); // Set up spatial index over query object SortedArray<RecordWithSpatialObject> queryIndex = new SortedArray.OfBaseRecord(); SpatialIndex<RecordWithSpatialObject> querySpatialIndex = SpatialIndex.newSpatialIndex(space, queryIndex); Record.Factory<RecordWithSpatialObject> recordFactory = new Record.Factory<RecordWithSpatialObject>() { @Override public RecordWithSpatialObject newRecord() { RecordWithSpatialObject record = new RecordWithSpatialObject(); record.spatialObject(spatialObject); return record; } }; querySpatialIndex.add(spatialObject, recordFactory, MAX_Z); spatialJoinIterator = spatialJoin.iterator(querySpatialIndex, dataSpatialIndex); if (LOG.isDebugEnabled()) { logQueryIndex(queryIndex); } } catch (IOException | InterruptedException e) { // These exceptions are declared by Geophile, but Geophile sits on top of FDB which should be // doing the right thing. throw new IllegalStateException(e); } spatialJoinCursor = new IteratorToCursorAdapter( new TransformingIterator<Pair<RecordWithSpatialObject, IndexRow>, IndexRow>(spatialJoinIterator) { @Override public IndexRow transform(Pair<RecordWithSpatialObject, IndexRow> pair) { return pair.right(); } }); } private GeophileIndex dataIndex(final QueryContext context, final boolean openEarly, final SpatialObject spatialObject) { final API.Ordering zOrdering = new API.Ordering(); IndexRowType rowType = keyRange.indexRowType().physicalRowType(); for (int f = 0; f < rowType.nFields(); f++) { zOrdering.append(new TPreparedField(rowType.typeAt(f), f), true); } GeophileIndex.CursorFactory cursorFactory = new GeophileIndex.CursorFactory() { @Override public GeophileCursor newCursor(GeophileIndex geophileIndex) { // About the assignment of a GeophileCursor to IndexCursorSpatial_InBox.geophileCursor: // The GeophileCursor has to be returned by GeophileIndex.CursorFactory.newCursor. // It is also needed by this class to support rebind. geophileCursor = new GeophileCursor(geophileIndex, openEarly); for (Map.Entry<Long, IndexKeyRange> entry : zKeyRanges(keyRange, spatialObject).entrySet()) { long z = entry.getKey(); IndexKeyRange zKeyRange = entry.getValue(); IterationHelper rowState = adapter.createIterationHelper(keyRange.indexRowType()); IndexCursorUnidirectional<ValueSource> zIntervalCursor = new IndexCursorUnidirectional<>(context, rowState, zKeyRange, zOrdering, ValueSortKeyAdapter.INSTANCE); geophileCursor.addCursor(z, zIntervalCursor); } return geophileCursor; } }; return new GeophileIndex(adapter, keyRange.indexRowType(), cursorFactory); } private Map<Long, IndexKeyRange> zKeyRanges(IndexKeyRange keyRange, SpatialObject spatialObject) { Map<Long, IndexKeyRange> zKeyRanges = new HashMap<>(); // The index column selector needs to select all the columns before the z column, and the z column itself. ColumnSelector indexColumnSelector = new IndexRowPrefixSelector(this.index.firstSpatialArgument() + 1); long[] zValues = new long[MAX_Z]; space.decompose(spatialObject, zValues); int zColumn = index.firstSpatialArgument(); Value toEnd = new Value(InternalIndexTypes.LONG.instance(false)); toEnd.putInt64(Long.MAX_VALUE); for (int i = 0; i < zValues.length && zValues[i] != SpaceImpl.Z_NULL; i++) { long z = zValues[i]; // Need to do an index lookup for z and each ancestor while (z != SpaceImpl.Z_NULL) { IndexKeyRange zKeyRange = zKeyRanges.get(z); if (zKeyRange == null) { IndexRowType physicalRowType = keyRange.indexRowType().physicalRowType(); int indexRowFields = physicalRowType.nFields(); SpatialIndexValueRecord zLoRow = new SpatialIndexValueRecord(indexRowFields); SpatialIndexValueRecord zHiRow = new SpatialIndexValueRecord(indexRowFields); IndexBound zLo = new IndexBound(zLoRow, indexColumnSelector); IndexBound zHi = new IndexBound(zHiRow, indexColumnSelector); // Take care of any equality restrictions before the spatial fields for (int f = 0; f < zColumn; f++) { ValueSource eqValueSource = loExpressions.value(f); zLoRow.value(f, eqValueSource); zHiRow.value(f, eqValueSource); } // lo and hi bounds Value loValue = new Value(InternalIndexTypes.LONG.instance(false)); loValue.putInt64(Space.zLo(z)); zLoRow.value(zColumn, loValue); zHiRow.value(zColumn, toEnd); zKeyRange = IndexKeyRange.bounded(physicalRowType, zLo, true, zHi, true); zKeyRanges.put(z, zKeyRange); z = z == SpaceImpl.Z_MIN ? SpaceImpl.Z_NULL : SpaceImpl.parent(z); } else { // If z is present, then so are all of its ancestors z = SpaceImpl.Z_NULL; } } } return zKeyRanges; } private SpatialObject spatialObject() { ValueRecord expressions = keyRange.lo().boundExpressions(context, bindings); Object object = expressions.value(index.firstSpatialArgument()).getObject(); if (object instanceof SpatialObject) { return (SpatialObject) object; } else if (object instanceof Geometry) { return JTS.spatialObject(space, (Geometry) object); } else { throw new IllegalStateException(String.format("Expected SpatialObject, found: %s", object.getClass())); } } private void logQueryIndex(SortedArray<RecordWithSpatialObject> queryIndex) throws IOException, InterruptedException { LOG.debug("Query index:"); Cursor<RecordWithSpatialObject> queryCursor = queryIndex.cursor(); RecordWithSpatialObject zMinRecord = queryIndex.newRecord(); zMinRecord.z(SpaceImpl.Z_MIN); queryCursor.goTo(zMinRecord); RecordWithSpatialObject queryRecord; while ((queryRecord = queryCursor.next()) != null) { LOG.debug(" {}", SpaceImpl.formatZ(queryRecord.z())); } } // Class state private static final SpatialJoin.Duplicates SPATIAL_JOIN_DUPLICATION = SpatialJoin.Duplicates.EXCLUDE; public static final int MAX_Z = 4; // SPATIAL_JOIN_LEFT/RIGHT_OBSERVERs are public so that they can be set by ITs public static SpatialJoin.InputObserver SPATIAL_JOIN_LEFT_OBSERVER = null; public static SpatialJoin.InputObserver SPATIAL_JOIN_RIGHT_OBSERVER = null; private static SpatialJoin.InputObserver SPATIAL_JOIN_RIGHT_DEBUG_OBSERVER = new SpatialJoin.InputObserver() { @Override public void randomAccess(Cursor cursor, long z) { LOG.debug("Random access using {}: {}", cursor, SpaceImpl.formatZ(z)); } @Override public void sequentialAccess(Cursor cursor, long zRandomAccess, Record record) { LOG.debug(" Sequential access using {} {} -> {}: {}", cursor, SpaceImpl.formatZ(zRandomAccess), record == null ? SpaceImpl.Z_NULL : SpaceImpl.formatZ(record.z()), record); } }; // Class state private static final Logger LOG = LoggerFactory.getLogger(IndexCursorSpatial_InBox.class); // Object state private final Space space; private final Index index; private final IndexKeyRange keyRange; private final boolean openEarly; private ValueRecord loExpressions; private GeophileCursor geophileCursor; private BindingsAwareCursor spatialJoinCursor; }