package me.prettyprint.cassandra.service; import com.google.common.collect.Iterators; import com.google.common.collect.PeekingIterator; import java.util.Iterator; import java.util.List; import me.prettyprint.cassandra.service.template.SliceFilter; import me.prettyprint.hector.api.beans.HColumn; import me.prettyprint.hector.api.query.SliceQuery; /** * Iterates over the column slice, refreshing until all qualifing columns are * retrieved. If column deletion can occur synchronously with calls to {@link #hasNext hasNext()}, * the column name object type must override Object.equals(). * * @author thrykol */ public class ColumnSliceIterator<K, N, V> implements Iterator<HColumn<N, V>> { private static final int DEFAULT_COUNT = 100; private SliceQuery<K, N, V> query; private PeekingIterator<HColumn<N, V>> iterator; private N start; private ColumnSliceFinish<N> finish; private SliceFilter<HColumn<N, V>> filter = null; private boolean reversed; private int count = DEFAULT_COUNT; private int columns = 0; /** * Constructor * * @param query Base SliceQuery to execute * @param start Starting point of the range * @param finish Finish point of the range. * @param reversed Whether or not the columns should be reversed */ public ColumnSliceIterator(SliceQuery<K, N, V> query, N start, final N finish, boolean reversed) { this(query, start, finish, reversed, DEFAULT_COUNT); } /** * Constructor * * @param query Base SliceQuery to execute * @param start Starting point of the range * @param finish Finish point of the range. * @param reversed Whether or not the columns should be reversed * @param count the amount of columns to retrieve per batch */ public ColumnSliceIterator(SliceQuery<K, N, V> query, N start, final N finish, boolean reversed, int count) { this(query, start, new ColumnSliceFinish<N>() { @Override public N function() { return finish; } }, reversed, count); } /** * Constructor * * @param query Base SliceQuery to execute * @param start Starting point of the range * @param finish Finish point of the range. Allows for a dynamically * determined point * @param reversed Whether or not the columns should be reversed */ public ColumnSliceIterator(SliceQuery<K, N, V> query, N start, ColumnSliceFinish<N> finish, boolean reversed) { this(query, start, finish, reversed, DEFAULT_COUNT); } /** * Constructor * * @param query Base SliceQuery to execute * @param start Starting point of the range * @param finish Finish point of the range. Allows for a dynamically * determined point * @param reversed Whether or not the columns should be reversed * @param count the amount of columns to retrieve per batch */ public ColumnSliceIterator(SliceQuery<K, N, V> query, N start, ColumnSliceFinish<N> finish, boolean reversed, int count) { this.query = query; this.start = start; this.finish = finish; this.reversed = reversed; this.count = count; this.query.setRange(this.start, this.finish.function(), this.reversed, this.count); } /** * Set a filter to determine which columns will be returned. * * @param filter Filter to determine which columns will be returned * @return <this> */ public ColumnSliceIterator setFilter(SliceFilter<HColumn<N, V>> filter) { this.filter = filter; return this; } @Override public boolean hasNext() { if (iterator == null) { iterator = Iterators.peekingIterator(query.execute().get().getColumns().iterator()); } else if (!iterator.hasNext() && columns == count) { // only need to do another query if maximum columns were retrieved refresh(); } while(filter != null && iterator != null && iterator.hasNext() && !filter.accept(iterator.peek())) { next(); if(!iterator.hasNext() && columns == count) { refresh(); } } return iterator.hasNext(); } @Override public HColumn<N, V> next() { HColumn<N, V> column = iterator.next(); start = column.getName(); columns++; return column; } @Override public void remove() { iterator.remove(); } public HColumn<N, V> peek() { if(hasNext()){ return iterator.peek(); } return null; } private void refresh() { query.setRange(start, finish.function(), reversed, count); columns = 0; List<HColumn<N, V>> list = query.execute().get().getColumns(); iterator = Iterators.peekingIterator(list.iterator()); if (iterator.hasNext()) { // The lower bound column may have been removed prior to the query executing, // so check to see if the first column returned by the current query is the same // as the lower bound column. If both columns are the same, skip the column N first = list.get(0).getName(); if (first.equals(start)) { next(); } } } /** * When iterating over a ColumnSlice, it may be desirable to move the finish * point for each query. This interface allows for a user defined function * which will return the new finish point. This is especially useful for * column families which have a TimeUUID as the column name. */ public interface ColumnSliceFinish<N> { /** * Generic function for deriving a new finish point. * * @return New finish point */ N function(); } }