/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed 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 co.cask.cdap.api.dataset.lib;
import co.cask.cdap.api.common.Bytes;
import co.cask.cdap.api.dataset.DatasetProperties;
import co.cask.cdap.data2.dataset2.DatasetFrameworkTestUtil;
import co.cask.cdap.proto.Id;
import co.cask.tephra.TransactionExecutor;
import com.google.common.collect.ImmutableList;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import java.lang.reflect.Type;
import java.util.List;
/**
* Test for {@link co.cask.cdap.api.dataset.lib.IndexedObjectStore}.
*/
public class IndexedObjectStoreTest {
@ClassRule
public static DatasetFrameworkTestUtil dsFrameworkUtil = new DatasetFrameworkTestUtil();
private static final Id.DatasetInstance index =
Id.DatasetInstance.from(DatasetFrameworkTestUtil.NAMESPACE_ID, "index");
@Before
public void createDataset() throws Exception {
createIndexedObjectStoreInstance(index, Feed.class);
}
@After
public void deleteDataset() throws Exception {
dsFrameworkUtil.deleteInstance(index);
}
@Test
public void testLookupByIndex() throws Exception {
final IndexedObjectStore<Feed> indexedFeed = dsFrameworkUtil.getInstance(index);
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(indexedFeed);
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
List<String> categories1 = ImmutableList.of("racing", "tech");
List<String> categories2 = ImmutableList.of("electronics", "tech");
Feed feed1 = new Feed("f1", "http://f1.com", categories1);
Feed feed2 = new Feed("apple", "http://apple.com", categories2);
byte[] key1 = Bytes.toBytes(feed1.getId());
byte[] key2 = Bytes.toBytes(feed2.getId());
indexedFeed.write(key1, feed1, getCategories(categories1));
indexedFeed.write(key2, feed2, getCategories(categories2));
List<Feed> feedResult1 = indexedFeed.readAllByIndex(Bytes.toBytes("racing"));
Assert.assertEquals(1, feedResult1.size());
List<Feed> feedResult2 = indexedFeed.readAllByIndex(Bytes.toBytes("running"));
Assert.assertEquals(0, feedResult2.size());
List<Feed> feedResult3 = indexedFeed.readAllByIndex(Bytes.toBytes("tech"));
Assert.assertEquals(2, feedResult3.size());
}
});
}
@Test
public void testIndexRewrites() throws Exception {
final IndexedObjectStore<Feed> indexedFeed = dsFrameworkUtil.getInstance(index);
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(indexedFeed);
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
List<String> categories1 = ImmutableList.of("big data", "startup");
List<String> categories2 = ImmutableList.of("hadoop");
Feed feed1 = new Feed("c1", "http://abc.com", categories1);
byte[] key1 = Bytes.toBytes(feed1.getId());
indexedFeed.write(key1, feed1, getCategories(categories1));
List<Feed> feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("big data"));
Assert.assertEquals(1, feedResult.size());
// re-write with new index values
indexedFeed.write(key1, feed1, getCategories(categories2));
//Should not return based on old index values
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("big data"));
Assert.assertEquals(0, feedResult.size());
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("hadoop"));
//Should return based on new indexValue
Assert.assertEquals(1, feedResult.size());
//Update with no index value. Lookup by any indexValue should not return the old entries
indexedFeed.write(key1, feed1);
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("hadoop"));
Assert.assertEquals(0, feedResult.size());
}
});
}
@Test
public void testIndexPruning() throws Exception {
final IndexedObjectStore<Feed> indexedFeed = dsFrameworkUtil.getInstance(index);
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(indexedFeed);
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
List<String> categories = ImmutableList.of("running", "marathon", "drinking");
Feed feed = new Feed("rocknroll", "http://rock'n'roll.com", categories);
byte[] key = Bytes.toBytes(feed.getId());
indexedFeed.write(key, feed, getCategories(categories));
List<Feed> feeds = indexedFeed.readAllByIndex(Bytes.toBytes("drinking"));
Assert.assertEquals(1, feeds.size());
indexedFeed.pruneIndex(key, Bytes.toBytes("drinking"));
feeds = indexedFeed.readAllByIndex(Bytes.toBytes("drinking"));
Assert.assertEquals(0, feeds.size());
}
});
}
@Test
public void testIndexNoSecondaryKeyChanges() throws Exception {
IndexedObjectStore<Feed> indexedFeed = dsFrameworkUtil.getInstance(index);
List<String> categories = ImmutableList.of("C++", "C#");
Feed feed1 = new Feed("MSFT", "http://microwsoft.com", categories);
byte[] key1 = Bytes.toBytes(feed1.getId());
indexedFeed.write(key1, feed1, getCategories(categories));
List<Feed> feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("C++"));
Assert.assertEquals(1, feedResult.size());
// re-write f1 with updated url but same id
Feed feed2 = new Feed("MSFT", "http://microsoft.com", categories);
indexedFeed.write(key1, feed2, getCategories(categories));
//Should be still be able to look up by secondary indices
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("C++"));
Assert.assertEquals(1, feedResult.size());
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("C#"));
Assert.assertEquals(1, feedResult.size());
Assert.assertEquals("http://microsoft.com", feedResult.get(0).getUrl());
}
@Test
public void testIndexUpdates() throws Exception {
final IndexedObjectStore<Feed> indexedFeed = dsFrameworkUtil.getInstance(index);
TransactionExecutor txnl = dsFrameworkUtil.newTransactionExecutor(indexedFeed);
txnl.execute(new TransactionExecutor.Subroutine() {
@Override
public void apply() throws Exception {
List<String> categories1 = ImmutableList.of("big data");
Feed feed1 = new Feed("a1", "http://apple.com", categories1);
byte[] key1 = Bytes.toBytes(feed1.getId());
indexedFeed.write(key1, feed1, getCategories(categories1));
List<Feed> feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("big data"));
Assert.assertEquals(1, feedResult.size());
indexedFeed.updateIndex(key1, Bytes.toBytes("startup"));
feedResult = indexedFeed.readAllByIndex(Bytes.toBytes("startup"));
Assert.assertEquals(1, feedResult.size());
}
});
}
protected void createIndexedObjectStoreInstance(Id.DatasetInstance datasetInstanceId, Type type) throws Exception {
dsFrameworkUtil.createInstance("indexedObjectStore", datasetInstanceId,
ObjectStores.objectStoreProperties(type, DatasetProperties.EMPTY));
}
private byte[][] getCategories(List<String> categories) {
byte[][] byteCategories = new byte[categories.size()][];
for (int i = 0; i < categories.size(); i++) {
byteCategories[i] = Bytes.toBytes(categories.get(i));
}
return byteCategories;
}
}