package org.hivedb.hibernate;
import org.hibernate.shards.strategy.access.SequentialShardAccessStrategy;
import org.hivedb.Hive;
import org.hivedb.HiveLockableException;
import org.hivedb.util.classgen.GenerateInstance;
import org.hivedb.util.classgen.GeneratedClassFactory;
import org.hivedb.util.classgen.GeneratedInstanceInterceptor;
import org.hivedb.util.classgen.ReflectionTools;
import org.hivedb.util.database.test.HiveTest;
import org.hivedb.util.database.test.HiveTest.Config;
import org.hivedb.util.database.test.WeatherReport;
import org.hivedb.util.functional.*;
import org.junit.Assert;
import static org.junit.Assert.*;
import org.junit.Test;
import java.util.*;
import java.util.Map.Entry;
@Config("hive_default")
public class BaseDataAccessObjectTest extends HiveTest {
private static Random random = new Random();
@SuppressWarnings("unchecked")
@Test
public void testGet() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport original = getPersistentInstance(dao);
WeatherReport report = dao.get(original.getReportId());
assertEquals(ReflectionTools.getDifferingFields(original, report, WeatherReport.class).toString(), original.hashCode(), report.hashCode());
}
@SuppressWarnings("unchecked")
@Test
public void testGetMissingRecord() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport original = getInstance(WeatherReport.class);
HiveIndexer indexer = new HiveIndexer(getHive());
indexer.insert(getEntityHiveConfig().getEntityConfig(WeatherReport.class), original);
assertTrue(dao.exists(original.getReportId()));
WeatherReport report = dao.get(original.getReportId());
assertFalse(dao.exists(original.getReportId()));
assertEquals(null, report);
// assertEquals(ReflectionTools.getDifferingFields(original, report, WeatherReport.class).toString(), original.hashCode(), report.hashCode());
}
@SuppressWarnings("unchecked")
@Test
public void testFindByProperty() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
dao.save(report);
int temperature = random.nextInt();
GeneratedInstanceInterceptor.setProperty(report, "temperature", temperature);
dao.save(report);
WeatherReport found = Atom.getFirstOrThrow(dao.findByProperty("temperature", temperature));
Assert.assertEquals(report.hashCode(), found.hashCode());
found = Atom.getFirstOrThrow(dao.findByProperty("regionCode", report.getRegionCode()));
assertEquals(report.hashCode(), found.hashCode());
found = Atom.getFirstOrThrow(dao.findByProperty("weatherEvents", Atom.getFirstOrThrow(report.getWeatherEvents()).getEventId()));
assertEquals(report.hashCode(), found.hashCode());
found = Atom.getFirstOrThrow(dao.findByProperty("continent", report.getContinent()));
assertEquals(report.hashCode(), found.hashCode());
found = Atom.getFirstOrThrow(dao.findByProperty("sources", Atom.getFirstOrThrow(report.getSources())));
assertEquals(report.hashCode(), found.hashCode());
// Test find by multiple properties
found = Atom.getFirstOrThrow(dao.findByProperties("regionCode", Transform.toMap(
new Entry[]{
new Pair<String, Object>("regionCode", report.getRegionCode()),
new Pair<String, Object>("weatherEvents", Atom.getFirstOrThrow(report.getWeatherEvents()).getEventId()),
})));
assertEquals(report.hashCode(), found.hashCode());
}
@SuppressWarnings("unchecked")
@Test
public void testFindByPropertyPaged() throws Exception {
final DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
Collection<WeatherReport> reports =
Generate.create(new Generator<WeatherReport>() {
public WeatherReport generate() {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "continent", "Derkaderkastan");
GeneratedInstanceInterceptor.setProperty(report, "temperature", 101);
GeneratedInstanceInterceptor.setProperty(report, "sources", Arrays.asList(new Integer[]{101, 102, 103}));
return dao.save(report);
}
}, new NumberIterator(12));
for (final String property : new String[]{"temperature", "sources"}) {
Assert.assertEquals(dao.findByProperty(property, 101).size(), 12);
final Collection<WeatherReport> results = Filter.grepUnique(Transform.flatten(Transform.map(new Unary<Integer, Collection<WeatherReport>>() {
public Collection<WeatherReport> f(Integer i) {
return dao.findByProperty(property, 101, (i - 1) * 4, 4);
}
}, new NumberIterator(3))));
Assert.assertEquals(
new HashSet<WeatherReport>(results).hashCode(),
new HashSet(reports).hashCode());
}
}
@SuppressWarnings("unchecked")
@Test
public void testFindByPropertyRange() throws Exception {
Collection<WeatherReport> reports =
Generate.create(new Generator<WeatherReport>() {
public WeatherReport generate() {
return new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
}
}, new NumberIterator(5));
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.saveAll(reports);
//Collection<WeatherReport> x = dao.findByProperty("temperature", Atom.getFirst(reports).getTemperature());
Integer min = Amass.min(
new Unary<WeatherReport, Integer>() {
public Integer f(WeatherReport weatherReport) {
return weatherReport.getTemperature();
}
},
reports,
Integer.class);
Integer max = Amass.max(
new Unary<WeatherReport, Integer>() {
public Integer f(WeatherReport weatherReport) {
return weatherReport.getTemperature();
}
},
reports,
Integer.class);
Collection<WeatherReport> range = dao.findByPropertyRange("temperature", min, max);
assertEquals(reports.size(), range.size());
Collection<WeatherReport> smallerRange =
dao.findByPropertyRange("temperature", Atom.getFirst(reports).getTemperature(), Atom.getFirst(reports).getTemperature());
assertEquals(1, smallerRange.size());
}
@SuppressWarnings("unchecked")
@Test
public void testFindByPropertyRangePaged() throws Exception {
final DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
final int INSTANCE_COUNT = 12;
Collection<WeatherReport> set = new HashSet<WeatherReport>();
int min = 0, max = 0;
for (int i = 0; i < INSTANCE_COUNT; i++) {
int temperature = random.nextInt();
min = Math.min(min, temperature);
max = Math.max(max, temperature);
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "temperature", temperature);
dao.save(report);
set.add(report);
}
final int finalMin = min;
final int finalMax = max;
Assert.assertEquals(
new HashSet<WeatherReport>(Transform.flatten(Transform.map(new Unary<Integer, Collection<WeatherReport>>() {
public Collection<WeatherReport> f(Integer i) {
final Collection<WeatherReport> value = dao.findByPropertyRange("temperature", finalMin, finalMax, (i - 1) * 4, 4);
return value;
}
}, new NumberIterator(3)))).hashCode(),
set.hashCode());
}
@SuppressWarnings("unchecked")
@Test
public void testGetCount() throws Exception {
final int temperature = random.nextInt();
final List<String> partitionDimensionKeys = Arrays.asList(new String[]{"Asia", "Andromida"});
Collection<WeatherReport> reports =
Generate.create(new Generator<WeatherReport>() {
public WeatherReport generate() {
WeatherReport weatherReport = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
// Set the same temperature for each partition dimension id. The partition dimension id will be calculated from the report id
GeneratedInstanceInterceptor.setProperty(weatherReport, "temperature", temperature + weatherReport.getReportId() % 2);
GeneratedInstanceInterceptor.setProperty(weatherReport, "continent", partitionDimensionKeys.get(weatherReport.getReportId() % 2));
GeneratedInstanceInterceptor.setProperty(weatherReport, "regionCode", 4);
return weatherReport;
}
}, new NumberIterator(5));
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.saveAll(reports);
Assert.assertEquals(dao.getCount("temperature", temperature) + dao.getCount("temperature", temperature + 1), 5);
Assert.assertEquals(dao.getCountByRange("temperature", temperature, temperature + 1), (Integer) 5);
Assert.assertEquals((Integer) (dao.getCountByProperties("temperature", Transform.toMap(
new Entry[]{
new Pair<String, Object>("temperature", temperature),
new Pair<String, Object>("regionCode", 4),
})) +
dao.getCountByProperties("temperature", Transform.toMap(
new Entry[]{
new Pair<String, Object>("temperature", temperature + 1),
new Pair<String, Object>("regionCode", 4),
}))), (Integer) 5);
}
@SuppressWarnings("unchecked")
@Test
public void testDelete() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport original = getPersistentInstance(dao);
dao.delete(original.getReportId());
assertNull(dao.get(original.getReportId()));
}
@SuppressWarnings("unchecked")
@Test
public void testInsert() {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
dao.save(report);
WeatherReport savedReport = dao.get(report.getReportId());
assertNotNull(savedReport);
assertEquals(report.hashCode(), savedReport.hashCode());
}
@SuppressWarnings("unchecked")
private Class getGeneratedClass() {
return GeneratedClassFactory.newInstance(WeatherReport.class).getClass();
}
private <T> T getInstance(Class<T> clazz) throws Exception {
return new GenerateInstance<T>(clazz).generate();
}
private WeatherReport getPersistentInstance(DataAccessObject<WeatherReport, Integer> dao) throws Exception {
return dao.save(getInstance(WeatherReport.class));
}
@SuppressWarnings("unchecked")
@Test
public void testUpdate() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
WeatherReport original = getPersistentInstance(dao);
WeatherReport updated = dao.get(original.getReportId());
GeneratedInstanceInterceptor.setProperty(updated, "latitude", new Double(30));
GeneratedInstanceInterceptor.setProperty(updated, "longitude", new Double(30));
/* TODO this fails because we currently don't support one-to-many relationships. We need to reenable the
deleteOrphanItems in our BaseDataAccessObject to support this
*/
/*
// Test collection item updates
List<WeatherEvent> weatherEvents = new ArrayList<WeatherEvent>(original.getWeatherEvents());
// Delete the first
weatherEvents.remove(0);
// Update the second
weatherEvents.get(0).setName("foobar");
// Add a third
weatherEvents.add(new GenerateInstance<WeatherEvent>(WeatherEvent.class).generate());
GeneratedInstanceInterceptor.setProperty(updated, "weatherEvents", weatherEvents);
*/
dao.save(updated);
//final WeatherReport persisted = dao.get(updated.getReportId());
assertFalse(updated.getLatitude().equals(original.getLatitude()));
// Check the updated collection
// size should be equal
//assertEquals(original.getWeatherEvents().size(), persisted.getWeatherEvents().size());
// first item should be removed
//assertFalse(Filter.grepItemAgainstList(Atom.getFirst(original.getWeatherEvents()), persisted.getWeatherEvents()));
// should be an updated item named foobar
/* assertTrue(Filter.grepItemAgainstList("foobar",
Transform.map(new Unary<WeatherEvent,String>() {
public String f(WeatherEvent weatherEvent) {
return weatherEvent.getName();
}}, persisted.getWeatherEvents()))); */
// new item should exist
//assertTrue(Filter.grepItemAgainstList(Atom.getLast(weatherEvents), persisted.getWeatherEvents()));
String newPrimaryIndexKey = findPrimaryKeyOnDifferentNode(original);
GeneratedInstanceInterceptor.setProperty(updated, "continent", new Integer(newPrimaryIndexKey).toString());
Assert.assertEquals(getHive().directory().getNodeIdsOfResourceId("WeatherReport", original.getReportId()).size(), 1);
dao.save(updated);
final Collection<Integer> nodeIdsOfResourceId = getHive().directory().getNodeIdsOfResourceId("WeatherReport", updated.getReportId());
// Make sure the resource is only on 1 hive node
Assert.assertEquals(nodeIdsOfResourceId.size(), 1);
// Make sure the node matches the new node
Assert.assertEquals(Atom.getFirstOrThrow(nodeIdsOfResourceId), Atom.getFirstOrThrow(getHive().directory().getNodeIdsOfPrimaryIndexKey(newPrimaryIndexKey)));
// Make sure the entity can be fetched on the new node
Assert.assertNotNull(dao.get(updated.getReportId()));
}
private String findPrimaryKeyOnDifferentNode(WeatherReport original)
throws HiveLockableException {
// Update the primary index key
int i = 1;
// Get a primary index key on a different node
for (; i < 100; i++) {
getHive().directory().insertPrimaryIndexKey(new Integer(i).toString());
if (!Atom.getFirstOrThrow(getHive().directory().getNodeIdsOfPrimaryIndexKey(new Integer(i).toString())).equals(
Atom.getFirstOrThrow(getHive().directory().getNodeIdsOfPrimaryIndexKey(original.getContinent()))))
break;
}
return new Integer(i).toString();
}
@SuppressWarnings("unchecked")
@Test
public void testSaveAll() throws Exception {
Collection<WeatherReport> reports = new ArrayList<WeatherReport>();
for (int i = 0; i < 54; i++) {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "reportId", i);
reports.add(report);
}
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.saveAll(reports);
for (WeatherReport report : reports)
assertEquals(report, dao.get(report.getReportId()));
}
@SuppressWarnings("unchecked")
@Test
public void testHealDataNodeOnlyRecord() throws Exception {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.save(report);
assertEquals(report, dao.get(report.getReportId()));
Hive hive = getHive();
hive.directory().deleteResourceId(config.getEntityConfig(getGeneratedClass()).getResourceName(), report.getReportId());
ReflectionTools.invokeSetter(report, "regionCode", report.getRegionCode() + 1);
assertFalse(dao.exists(report.getReportId()));
dao.save(report);
assertEquals(report, dao.get(report.getReportId()));
}
@SuppressWarnings("unchecked")
@Test
public void testHealDataNodeOnlyRecordSaveAll() throws Exception {
Collection<WeatherReport> reports = new ArrayList<WeatherReport>();
for (int i = 0; i < 5; i++) {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "reportId", i);
reports.add(report);
}
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.saveAll(reports);
for (WeatherReport report : reports)
assertEquals(report, dao.get(report.getReportId()));
WeatherReport orphan = Atom.getFirstOrThrow(reports);
Hive hive = getHive();
hive.directory().deleteResourceId(config.getEntityConfig(getGeneratedClass()).getResourceName(), orphan.getReportId());
for (WeatherReport report : reports)
ReflectionTools.invokeSetter(report, "regionCode", report.getRegionCode() + 1);
assertFalse(dao.exists(orphan.getReportId()));
dao.saveAll(reports);
for (WeatherReport report : reports)
assertEquals(report, dao.get(report.getReportId()));
HiveIndexer indexer = new HiveIndexer(getHive());
dao.delete(orphan.getReportId());
indexer.insert(config.getEntityConfig(dao.getRespresentedClass()), orphan);
dao.saveAll(reports);
for (WeatherReport report : reports)
assertEquals(report, dao.get(report.getReportId()));
}
@SuppressWarnings("unchecked")
@Test
public void testUpdateAll() throws Exception {
Collection<WeatherReport> reports = new ArrayList<WeatherReport>();
for (int i = 0; i < 5; i++) {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "reportId", i);
reports.add(report);
}
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
dao.saveAll(reports);
Collection<WeatherReport> updated = new ArrayList<WeatherReport>();
for (WeatherReport report : reports) {
GeneratedInstanceInterceptor.setProperty(report, "temperature", 100);
updated.add(report);
}
dao.saveAll(updated);
for (WeatherReport report : updated) {
final WeatherReport weatherReport = dao.get(report.getReportId());
assertEquals(report, weatherReport);
}
// Test changing the partition dimension key
String newPrimaryIndexKey = findPrimaryKeyOnDifferentNode(Atom.getFirstOrThrow(reports));
for (WeatherReport report : reports) {
GeneratedInstanceInterceptor.setProperty(report, "continent", new Integer(newPrimaryIndexKey).toString());
Assert.assertEquals(getHive().directory().getNodeIdsOfResourceId("WeatherReport", report.getReportId()).size(), 1);
}
dao.saveAll(reports);
for (WeatherReport report : reports) {
final Collection<Integer> nodeIdsOfResourceId = getHive().directory().getNodeIdsOfResourceId("WeatherReport", report.getReportId());
// Make sure the resource is only on 1 hive node
Assert.assertEquals(nodeIdsOfResourceId.size(), 1);
// Make sure the node matches the new node
Assert.assertEquals(Atom.getFirstOrThrow(nodeIdsOfResourceId), Atom.getFirstOrThrow(getHive().directory().getNodeIdsOfPrimaryIndexKey(newPrimaryIndexKey)));
// Make sure the entity can be fetched on the new node
Assert.assertNotNull(dao.get(report.getReportId()));
}
}
@SuppressWarnings("unchecked")
@Test
public void testExists() throws Exception {
DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
assertFalse(dao.exists(88));
WeatherReport original = getPersistentInstance(dao);
assertTrue(dao.exists(original.getReportId()));
}
@SuppressWarnings("unchecked")
@Test
public void testAllShardsQuery() {
final DataAccessObject<WeatherReport, Integer> dao = (DataAccessObject<WeatherReport, Integer>) getDao(getGeneratedClass());
final int INSTANCES = 20;
Collection<WeatherReport> reports = Generate.create(new Generator<WeatherReport>() {
public WeatherReport generate() {
WeatherReport report = new GenerateInstance<WeatherReport>(WeatherReport.class).generate();
GeneratedInstanceInterceptor.setProperty(report, "latitude", 1d);
GeneratedInstanceInterceptor.setProperty(report, "regionCode", 1); // used to verify node spread
dao.save(report);
return report;
}
}, new NumberIterator(INSTANCES));
Hive hive = getHive();
// Make sure we saved to > 1 node
Assert.assertTrue(1 < hive.directory().getNodeIdsOfSecondaryIndexKey("WeatherReport", "regionCode", Atom.getFirstOrThrow(reports).getRegionCode()).size());
// Make sure all instances are found across nodes
Assert.assertEquals(INSTANCES, dao.findByProperty("latitude", 1d).size());
}
/**
* Test illustrating bug 6520-6522.
* <p/>
* The count code issues a count query to each node. The broken code was then looking only at the count returned by the first node. This was
* only the correct behavior 1/n percent of the time. The correct behavior is to sum the counts from all the nodes.
*/
@Test
public void shouldSumCountsFromAllNodes() throws Exception {
final QueryCountStubDAO dao = new QueryCountStubDAO(getGeneratedClass());
assertEquals(5, dao.getCount("foo", "bar").intValue());
assertEquals(5, dao.getCount("foo", "bar").intValue());
assertEquals(5, dao.getCount("foo", "bar").intValue());
assertEquals(3, dao.callCount);
}
/**
* Overrides the counts returned in support of the shouldSumCountsFromAllNodes test.
*/
public class QueryCountStubDAO extends BaseDataAccessObject {
int callCount = 0;
public QueryCountStubDAO(Class clazz) {
super(
config.getEntityConfig(clazz),
hive,
new HiveSessionFactoryBuilderImpl(
config,
getMappedClasses(),
hive,
new SequentialShardAccessStrategy()));
}
@Override
protected Collection<Object> queryByProperties(String partitioningPropertyName, Map<String, Object> propertyNameValueMap, Integer firstResult, Integer maxResults, boolean justCount) {
ArrayList<Object> list = new ArrayList<Object>();
switch (callCount++) {
case 0:
list.add(0);
list.add(5);
return list;
case 1:
list.add(5);
list.add(0);
return list;
case 2:
list.add(2);
list.add(3);
return list;
default:
throw new RuntimeException("unexpect call");
}
}
}
}