ChooseBestMatchIndexQueryStrategy implements IndexQueryStrategySPI { @Override public CloseableIterator<Index> getIndices( final Map<StatisticsId, InternalDataStatistics<SimpleFeature, ?, ?>> stats, final QueryConstraints query, final Index[] indices, final DataTypeAdapter<?> adapter, final Map<QueryHint, Object> hints) { return new CloseableIterator<Index>() { Index nextIdx = null; boolean done = false; int i = 0; @Override public boolean hasNext() { long min = Long.MAX_VALUE; Index bestIdx = null; while (!done && (i < indices.length)) { nextIdx = indices[i++]; if (nextIdx.getIndexStrategy().getOrderedDimensionDefinitions().length == 0) { continue; } final List<MultiDimensionalNumericData> constraints = query.getIndexConstraints(nextIdx); boolean containsRowRangeHistograms = false; final StatisticsQuery<NumericHistogram> query = VectorStatisticsQueryBuilder.newBuilder().factory().rowHistogram().indexName( nextIdx.getName()).build(); for (final StatisticsId statsId : stats.keySet()) { if (statsId.getType().equals(query.getStatsType()) && statsId.getExtendedId().startsWith(query.getExtendedId())) { containsRowRangeHistograms = true; break; } } if (!containsRowRangeHistograms) { LOGGER.warn( "Best Match Heuristic requires statistic RowRangeHistogramStatistics for each index to properly choose an index."); } if (IndexUtils.isFullTableScan(constraints)) { if (bestIdx == null) { bestIdx = nextIdx; } } else { final int maxRangeDecomposition; if (hints.containsKey(QueryHint.MAX_RANGE_DECOMPOSITION)) { maxRangeDecomposition = (Integer) hints.get(QueryHint.MAX_RANGE_DECOMPOSITION); } else { LOGGER.warn( "No max range decomposition hint was provided, this should be provided from the data store options"); maxRangeDecomposition = 2000; } final QueryRanges ranges = DataStoreUtils.constraintsToQueryRanges( constraints, nextIdx, null, maxRangeDecomposition); final long temp = DataStoreUtils.cardinality(nextIdx, stats, ranges); if (temp < min) { bestIdx = nextIdx; min = temp; } } } nextIdx = bestIdx; done = true; return nextIdx != null; } @Override public Index next() throws NoSuchElementException { if (nextIdx == null) { throw new NoSuchElementException(); } final Index returnVal = nextIdx; nextIdx = null; return returnVal; } @Override public void remove() {} @Override public void close() {} }; }  @Override String toString(); @Override CloseableIterator<Index> getIndices(
      final Map<StatisticsId, InternalDataStatistics<SimpleFeature, ?, ?>> stats,
      final QueryConstraints query,
      final Index[] indices,
      final DataTypeAdapter<?> adapter,
      final Map<QueryHint, Object> hints); @Override boolean requiresStats(); static final String NAME; }
@Test public void testChooseSpatialTemporalWithStats() { final Index temporalindex = new SpatialTemporalIndexBuilder().createIndex(); final Index spatialIndex = new SpatialIndexBuilder().createIndex(); final RowRangeHistogramStatistics<SimpleFeature> rangeTempStats = new RowRangeHistogramStatistics<>(null, temporalindex.getName(), null); final RowRangeHistogramStatistics<SimpleFeature> rangeStats = new RowRangeHistogramStatistics<>(null, spatialIndex.getName(), null); final Map<StatisticsId, InternalDataStatistics<SimpleFeature, ?, ?>> statsMap = new HashMap<>(); statsMap.put( VectorStatisticsQueryBuilder.newBuilder().factory().rowHistogram().indexName( spatialIndex.getName()).build().getId(), rangeStats); statsMap.put( VectorStatisticsQueryBuilder.newBuilder().factory().rowHistogram().indexName( temporalindex.getName()).build().getId(), rangeTempStats); final ChooseBestMatchIndexQueryStrategy strategy = new ChooseBestMatchIndexQueryStrategy(); final ConstraintSet cs1 = new ConstraintSet(); cs1.addConstraint( LatitudeDefinition.class, new ConstraintData(new ConstrainedIndexValue(0.3, 0.5), true)); cs1.addConstraint( LongitudeDefinition.class, new ConstraintData(new ConstrainedIndexValue(0.4, 0.7), true)); final ConstraintSet cs2a = new ConstraintSet(); cs2a.addConstraint( TimeDefinition.class, new ConstraintData(new ConstrainedIndexValue(0.1, 0.2), true)); final ConstraintsByClass constraints = new ConstraintsByClass(Arrays.asList(cs2a)).merge(Collections.singletonList(cs1)); final BasicQueryByClass query = new BasicQueryByClass(constraints); final NumericIndexStrategy temporalIndexStrategy = new SpatialTemporalIndexBuilder().createIndex().getIndexStrategy(); final Random r = new Random(SEED); for (int i = 0; i < ROWS; i++) { final double x = r.nextDouble(); final double y = r.nextDouble(); final double t = r.nextDouble(); final InsertionIds id = temporalIndexStrategy.getInsertionIds( new BasicNumericDataset( new NumericData[] { new NumericValue(x), new NumericValue(y), new NumericValue(t)})); for (final SinglePartitionInsertionIds range : id.getPartitionKeys()) { rangeTempStats.entryIngested( null, new GeoWaveRowImpl( new GeoWaveKeyImpl( new byte[] {1}, (short) 1, range.getPartitionKey(), range.getSortKeys().get(0), 0), new GeoWaveValue[] {})); } } final Index index = new SpatialIndexBuilder().createIndex(); final NumericIndexStrategy indexStrategy = index.getIndexStrategy(); for (int i = 0; i < ROWS; i++) { final double x = r.nextDouble(); final double y = r.nextDouble(); final double t = r.nextDouble(); final InsertionIds id = indexStrategy.getInsertionIds( new BasicNumericDataset( new NumericData[] { new NumericValue(x), new NumericValue(y), new NumericValue(t)})); for (final SinglePartitionInsertionIds range : id.getPartitionKeys()) { rangeStats.entryIngested( null, new GeoWaveRowImpl( new GeoWaveKeyImpl( new byte[] {1}, (short) 1, range.getPartitionKey(), range.getSortKeys().get(0), 0), new GeoWaveValue[] {})); } } final Iterator<Index> it = getIndices(statsMap, query, strategy); assertTrue(it.hasNext()); assertEquals(temporalindex.getName(), it.next().getName()); assertFalse(it.hasNext()); }