/**
     * This produces iterators of Cursor objects that must be fully processed (until isDone()
     * returns true) before the next Cursor is processed. It is *not* safe to pass these cursors off
     * to another thread for parallel processing
     *
     * @return
     */
    @Override
    public Iterator<Cursor> iterator() {
      final Map<String, Object> metricCacheMap = Maps.newHashMap();
      final IndexedLongs timestamps = index.getReadOnlyTimestamps();

      final FunctionalIterator<Cursor> retVal =
          FunctionalIterator.create(
                  gran.iterable(interval.getStartMillis(), interval.getEndMillis()).iterator())
              .transform(
                  new Function<Long, Cursor>() {
                    private int currRow = 0;

                    @Override
                    public Cursor apply(final Long input) {
                      final long timeStart = Math.max(interval.getStartMillis(), input);
                      while (currRow < timestamps.size() && timestamps.get(currRow) < timeStart) {
                        ++currRow;
                      }

                      return new Cursor() {
                        private final DateTime myBucket = gran.toDateTime(input);
                        private final long nextBucket =
                            Math.min(gran.next(myBucket.getMillis()), interval.getEndMillis());
                        private final int initRow = currRow;

                        @Override
                        public DateTime getTime() {
                          return myBucket;
                        }

                        @Override
                        public void advance() {
                          ++currRow;
                        }

                        @Override
                        public boolean isDone() {
                          return currRow >= timestamps.size()
                              || timestamps.get(currRow) >= nextBucket;
                        }

                        @Override
                        public void reset() {
                          currRow = initRow;
                        }

                        @Override
                        public DimensionSelector makeDimensionSelector(final String dimensionName) {
                          final Indexed<? extends IndexedInts> rowVals =
                              index.getDimColumn(dimensionName);
                          final Indexed<String> dimValueLookup =
                              index.getDimValueLookup(dimensionName);

                          if (rowVals == null) {
                            return null;
                          }

                          return new DimensionSelector() {
                            @Override
                            public IndexedInts getRow() {
                              return rowVals.get(currRow);
                            }

                            @Override
                            public int getValueCardinality() {
                              return dimValueLookup.size();
                            }

                            @Override
                            public String lookupName(int id) {
                              final String retVal = dimValueLookup.get(id);
                              return retVal == null ? "" : retVal;
                            }

                            @Override
                            public int lookupId(String name) {
                              return ("".equals(name))
                                  ? dimValueLookup.indexOf(null)
                                  : dimValueLookup.indexOf(name);
                            }
                          };
                        }

                        @Override
                        public FloatMetricSelector makeFloatMetricSelector(String metricName) {
                          IndexedFloats cachedMetricVals =
                              (IndexedFloats) metricCacheMap.get(metricName);

                          if (cachedMetricVals == null) {
                            final MetricHolder metricHolder = index.getMetricHolder(metricName);
                            if (metricHolder != null) {
                              cachedMetricVals = metricHolder.getFloatType();
                              if (cachedMetricVals != null) {
                                metricCacheMap.put(metricName, cachedMetricVals);
                              }
                            }
                          }

                          if (cachedMetricVals == null) {
                            return new FloatMetricSelector() {
                              @Override
                              public float get() {
                                return 0.0f;
                              }
                            };
                          }

                          final IndexedFloats metricVals = cachedMetricVals;
                          return new FloatMetricSelector() {
                            @Override
                            public float get() {
                              return metricVals.get(currRow);
                            }
                          };
                        }

                        @Override
                        public ComplexMetricSelector makeComplexMetricSelector(String metricName) {
                          Indexed cachedMetricVals = (Indexed) metricCacheMap.get(metricName);

                          if (cachedMetricVals == null) {
                            final MetricHolder metricHolder = index.getMetricHolder(metricName);

                            if (metricHolder != null) {
                              cachedMetricVals = metricHolder.getComplexType();
                              if (cachedMetricVals != null) {
                                metricCacheMap.put(metricName, cachedMetricVals);
                              }
                            }
                          }

                          if (cachedMetricVals == null) {
                            return null;
                          }

                          final Indexed metricVals = cachedMetricVals;
                          return new ComplexMetricSelector() {
                            @Override
                            public Class classOfObject() {
                              return metricVals.getClazz();
                            }

                            @Override
                            public Object get() {
                              return metricVals.get(currRow);
                            }
                          };
                        }
                      };
                    }
                  });

      return MoreIterators.after(
          retVal,
          new Runnable() {
            @Override
            public void run() {
              Closeables.closeQuietly(timestamps);
              for (Object object : metricCacheMap.values()) {
                if (object instanceof Closeable) {
                  Closeables.closeQuietly((Closeable) object);
                }
              }
            }
          });
    }
    @Override
    public Iterator<Cursor> iterator() {
      final Offset baseOffset = offset.clone();

      final Map<String, Object> metricHolderCache = Maps.newHashMap();
      final IndexedLongs timestamps = index.getReadOnlyTimestamps();

      final FunctionalIterator<Cursor> retVal =
          FunctionalIterator.create(
                  gran.iterable(interval.getStartMillis(), interval.getEndMillis()).iterator())
              .transform(
                  new Function<Long, Cursor>() {

                    @Override
                    public Cursor apply(final Long input) {
                      final long timeStart = Math.max(interval.getStartMillis(), input);
                      while (baseOffset.withinBounds()
                          && timestamps.get(baseOffset.getOffset()) < timeStart) {
                        baseOffset.increment();
                      }

                      final Offset offset =
                          new TimestampCheckingOffset(
                              baseOffset,
                              timestamps,
                              Math.min(interval.getEndMillis(), gran.next(timeStart)));

                      return new Cursor() {
                        private final Offset initOffset = offset.clone();
                        private final DateTime myBucket = gran.toDateTime(input);
                        private Offset cursorOffset = offset;

                        @Override
                        public DateTime getTime() {
                          return myBucket;
                        }

                        @Override
                        public void advance() {
                          cursorOffset.increment();
                        }

                        @Override
                        public boolean isDone() {
                          return !cursorOffset.withinBounds();
                        }

                        @Override
                        public void reset() {
                          cursorOffset = initOffset.clone();
                        }

                        @Override
                        public DimensionSelector makeDimensionSelector(final String dimensionName) {
                          final Indexed<? extends IndexedInts> rowVals =
                              index.getDimColumn(dimensionName);
                          final Indexed<String> dimValueLookup =
                              index.getDimValueLookup(dimensionName);

                          if (rowVals == null) {
                            return null;
                          }

                          return new DimensionSelector() {
                            @Override
                            public IndexedInts getRow() {
                              return rowVals.get(cursorOffset.getOffset());
                            }

                            @Override
                            public int getValueCardinality() {
                              return dimValueLookup.size();
                            }

                            @Override
                            public String lookupName(int id) {
                              final String retVal = dimValueLookup.get(id);
                              return retVal == null ? "" : retVal;
                            }

                            @Override
                            public int lookupId(String name) {
                              return ("".equals(name))
                                  ? dimValueLookup.indexOf(null)
                                  : dimValueLookup.indexOf(name);
                            }
                          };
                        }

                        @Override
                        public FloatMetricSelector makeFloatMetricSelector(String metricName) {
                          IndexedFloats cachedMetricVals =
                              (IndexedFloats) metricHolderCache.get(metricName);

                          if (cachedMetricVals == null) {
                            MetricHolder holder = index.getMetricHolder(metricName);
                            if (holder != null) {
                              cachedMetricVals = holder.getFloatType();
                              metricHolderCache.put(metricName, cachedMetricVals);
                            }
                          }

                          if (cachedMetricVals == null) {
                            return new FloatMetricSelector() {
                              @Override
                              public float get() {
                                return 0.0f;
                              }
                            };
                          }

                          final IndexedFloats metricVals = cachedMetricVals;
                          return new FloatMetricSelector() {
                            @Override
                            public float get() {
                              return metricVals.get(cursorOffset.getOffset());
                            }
                          };
                        }

                        @Override
                        public ComplexMetricSelector makeComplexMetricSelector(String metricName) {
                          Indexed cachedMetricVals = (Indexed) metricHolderCache.get(metricName);

                          if (cachedMetricVals == null) {
                            MetricHolder holder = index.getMetricHolder(metricName);
                            if (holder != null) {
                              cachedMetricVals = holder.getComplexType();
                              metricHolderCache.put(metricName, cachedMetricVals);
                            }
                          }

                          if (cachedMetricVals == null) {
                            return null;
                          }

                          final Indexed metricVals = cachedMetricVals;
                          return new ComplexMetricSelector() {
                            @Override
                            public Class classOfObject() {
                              return metricVals.getClazz();
                            }

                            @Override
                            public Object get() {
                              return metricVals.get(cursorOffset.getOffset());
                            }
                          };
                        }
                      };
                    }
                  });

      // This after call is not perfect, if there is an exception during processing, it will never
      // get called,
      // but it's better than nothing and doing this properly all the time requires a lot more
      // fixerating
      return MoreIterators.after(
          retVal,
          new Runnable() {
            @Override
            public void run() {
              Closeables.closeQuietly(timestamps);
              for (Object object : metricHolderCache.values()) {
                if (object instanceof Closeable) {
                  Closeables.closeQuietly((Closeable) object);
                }
              }
            }
          });
    }