/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.accumulo.core.iterators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.system.MultiIterator;
import org.apache.hadoop.io.Text;
import org.junit.Test;
public class AggregatingIteratorTest {
private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<>();
/**
* @deprecated since 1.4; visible only for testing
*/
@Deprecated
public static class SummationAggregator implements org.apache.accumulo.core.iterators.aggregation.Aggregator {
int sum;
@Override
public Value aggregate() {
return new Value((sum + "").getBytes());
}
@Override
public void collect(Value value) {
int val = Integer.parseInt(value.toString());
sum += val;
}
@Override
public void reset() {
sum = 0;
}
}
static Key newKey(int row, int colf, int colq, long ts, boolean deleted) {
Key k = newKey(row, colf, colq, ts);
k.setDeleted(true);
return k;
}
static Key newKey(int row, int colf, int colq, long ts) {
return new Key(newRow(row), new Text(String.format("cf%03d", colf)), new Text(String.format("cq%03d", colq)), ts);
}
static Range newRow(int row, int colf, int colq, long ts, boolean inclusive) {
return new Range(newKey(row, colf, colq, ts), inclusive, null, true);
}
static Range newRow(int row, int colf, int colq, long ts) {
return newRow(row, colf, colq, ts, true);
}
static void newKeyValue(TreeMap<Key,Value> tm, int row, int colf, int colq, long ts, boolean deleted, String val) {
Key k = newKey(row, colf, colq, ts);
k.setDeleted(deleted);
tm.put(k, new Value(val.getBytes()));
}
static Text newRow(int row) {
return new Text(String.format("r%03d", row));
}
@SuppressWarnings("deprecation")
@Test
public void test1() throws IOException {
TreeMap<Key,Value> tm1 = new TreeMap<>();
// keys that do not aggregate
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
newKeyValue(tm1, 1, 1, 1, 2, false, "3");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> emptyMap = Collections.emptyMap();
ai.init(new SortedMapIterator(tm1), emptyMap, null);
ai.seek(new Range(), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("4", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 1), ai.getTopKey());
assertEquals("2", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// try seeking
ai.seek(newRow(1, 1, 1, 2), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 1), ai.getTopKey());
assertEquals("2", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// seek after everything
ai.seek(newRow(1, 1, 1, 0), EMPTY_COL_FAMS, false);
assertFalse(ai.hasTop());
}
@SuppressWarnings("deprecation")
@Test
public void test2() throws IOException {
TreeMap<Key,Value> tm1 = new TreeMap<>();
// keys that aggregate
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
newKeyValue(tm1, 1, 1, 1, 2, false, "3");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
ai.init(new SortedMapIterator(tm1), opts, null);
ai.seek(new Range(), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// try seeking to the beginning of a key that aggregates
ai.seek(newRow(1, 1, 1, 3), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// try seeking the middle of a key the aggregates
ai.seek(newRow(1, 1, 1, 2), EMPTY_COL_FAMS, false);
assertFalse(ai.hasTop());
// try seeking to the end of a key the aggregates
ai.seek(newRow(1, 1, 1, 1), EMPTY_COL_FAMS, false);
assertFalse(ai.hasTop());
// try seeking before a key the aggregates
ai.seek(newRow(1, 1, 1, 4), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
}
@SuppressWarnings("deprecation")
@Test
public void test3() throws IOException {
TreeMap<Key,Value> tm1 = new TreeMap<>();
// keys that aggregate
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
newKeyValue(tm1, 1, 1, 1, 2, false, "3");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
// keys that do not aggregate
newKeyValue(tm1, 2, 2, 1, 1, false, "2");
newKeyValue(tm1, 2, 2, 1, 2, false, "3");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
ai.init(new SortedMapIterator(tm1), opts, null);
ai.seek(new Range(), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 1), ai.getTopKey());
assertEquals("2", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// seek after key that aggregates
ai.seek(newRow(1, 1, 1, 2), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
// seek before key that aggregates
ai.seek(newRow(1, 1, 1, 4), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
}
@SuppressWarnings("deprecation")
@Test
public void test4() throws IOException {
TreeMap<Key,Value> tm1 = new TreeMap<>();
// keys that do not aggregate
newKeyValue(tm1, 0, 0, 1, 1, false, "7");
// keys that aggregate
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
newKeyValue(tm1, 1, 1, 1, 2, false, "3");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
// keys that do not aggregate
newKeyValue(tm1, 2, 2, 1, 1, false, "2");
newKeyValue(tm1, 2, 2, 1, 2, false, "3");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
ai.init(new SortedMapIterator(tm1), opts, null);
ai.seek(new Range(), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(0, 0, 1, 1), ai.getTopKey());
assertEquals("7", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 1), ai.getTopKey());
assertEquals("2", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
// seek test
ai.seek(newRow(0, 0, 1, 0), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 3), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
// seek after key that aggregates
ai.seek(newRow(1, 1, 1, 2), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(2, 2, 1, 2), ai.getTopKey());
assertEquals("3", ai.getTopValue().toString());
}
@SuppressWarnings("deprecation")
@Test
public void test5() throws IOException {
// try aggregating across multiple data sets that contain
// the exact same keys w/ different values
TreeMap<Key,Value> tm1 = new TreeMap<>();
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
TreeMap<Key,Value> tm2 = new TreeMap<>();
newKeyValue(tm2, 1, 1, 1, 1, false, "3");
TreeMap<Key,Value> tm3 = new TreeMap<>();
newKeyValue(tm3, 1, 1, 1, 1, false, "4");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
List<SortedKeyValueIterator<Key,Value>> sources = new ArrayList<>(3);
sources.add(new SortedMapIterator(tm1));
sources.add(new SortedMapIterator(tm2));
sources.add(new SortedMapIterator(tm3));
MultiIterator mi = new MultiIterator(sources, true);
ai.init(mi, opts, null);
ai.seek(new Range(), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 1), ai.getTopKey());
assertEquals("9", ai.getTopValue().toString());
}
@SuppressWarnings("deprecation")
@Test
public void test6() throws IOException {
TreeMap<Key,Value> tm1 = new TreeMap<>();
// keys that aggregate
newKeyValue(tm1, 1, 1, 1, 1, false, "2");
newKeyValue(tm1, 1, 1, 1, 2, false, "3");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
ai.init(new SortedMapIterator(tm1), opts, new DefaultIteratorEnvironment());
// try seeking to the beginning of a key that aggregates
ai.seek(newRow(1, 1, 1, 3, false), EMPTY_COL_FAMS, false);
assertFalse(ai.hasTop());
}
@SuppressWarnings("deprecation")
@Test
public void test7() throws IOException {
// test that delete is not aggregated
TreeMap<Key,Value> tm1 = new TreeMap<>();
newKeyValue(tm1, 1, 1, 1, 2, true, "");
newKeyValue(tm1, 1, 1, 1, 3, false, "4");
newKeyValue(tm1, 1, 1, 1, 4, false, "3");
AggregatingIterator ai = new AggregatingIterator();
Map<String,String> opts = new HashMap<>();
opts.put("cf001", SummationAggregator.class.getName());
ai.init(new SortedMapIterator(tm1), opts, new DefaultIteratorEnvironment());
ai.seek(newRow(1, 1, 1, 4, true), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 4), ai.getTopKey());
assertEquals("7", ai.getTopValue().toString());
ai.next();
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 2, true), ai.getTopKey());
assertEquals("", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
tm1 = new TreeMap<>();
newKeyValue(tm1, 1, 1, 1, 2, true, "");
ai = new AggregatingIterator();
ai.init(new SortedMapIterator(tm1), opts, new DefaultIteratorEnvironment());
ai.seek(newRow(1, 1, 1, 4, true), EMPTY_COL_FAMS, false);
assertTrue(ai.hasTop());
assertEquals(newKey(1, 1, 1, 2, true), ai.getTopKey());
assertEquals("", ai.getTopValue().toString());
ai.next();
assertFalse(ai.hasTop());
}
}