package org.hivedb.meta.directory;
import org.hivedb.DirectoryCorruptionException;
import org.hivedb.HiveKeyNotFoundException;
import org.hivedb.Lockable.Status;
import org.hivedb.meta.Node;
import org.hivedb.meta.PartitionDimension;
import org.hivedb.meta.Resource;
import org.hivedb.meta.SecondaryIndex;
import org.hivedb.meta.persistence.CachingDataSourceProvider;
import org.hivedb.util.QuickCache;
import org.hivedb.util.database.JdbcTypeMapper;
import org.hivedb.util.database.RowMappers;
import org.hivedb.util.database.Schemas;
import org.hivedb.util.database.Statements;
import org.hivedb.util.functional.Atom;
import org.hivedb.util.functional.Delay;
import org.hivedb.util.functional.Unary;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.Collection;
import java.util.Map;
public class DbDirectory extends SimpleJdbcDaoSupport implements NodeResolver, Directory {
private static QuickCache cache = new QuickCache();
private PartitionDimension partitionDimension;
private IndexSqlFormatter sql = new IndexSqlFormatter();
public DbDirectory(PartitionDimension dimension, DataSource dataSource) {
this.partitionDimension = dimension;
this.setDataSource(dataSource);
}
public DbDirectory(PartitionDimension dimension) {
this.partitionDimension = dimension;
this.setDataSource(CachingDataSourceProvider.getInstance().getDataSource(dimension.getIndexUri()));
}
public PartitionDimension getPartitionDimension() {
return this.partitionDimension;
}
public Object insertPrimaryIndexKey(final Node node, final Object primaryIndexKey) {
return newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
int[] types = new int[]{JdbcTypeMapper.primitiveTypeToJdbcType(primaryIndexKey.getClass()), Types.INTEGER};
Object[] parameters = new Object[]{primaryIndexKey, node.getId()};
if (lockPrimaryKeyForInsert(primaryIndexKey, node))
doUpdate(sql.insertPrimaryIndexKey(partitionDimension), types, parameters);
return primaryIndexKey;
}
});
}
public Object insertSecondaryIndexKey(final SecondaryIndex secondaryIndex, final Object secondaryIndexKey, final Object resourceId) {
return newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
return insertSecondaryIndexKeyNoTransaction(secondaryIndex, secondaryIndexKey, resourceId);
}
});
}
Object insertSecondaryIndexKeyNoTransaction(final SecondaryIndex secondaryIndex, final Object secondaryIndexKey, final Object resourceId) {
Object[] parameters = new Object[]{secondaryIndexKey, resourceId};
int[] types = new int[]{secondaryIndex.getColumnInfo().getColumnType(), secondaryIndex.getResource().getColumnType()};
if (lockSecondaryIndexKey(secondaryIndex, secondaryIndexKey, resourceId))
doUpdate(sql.insertSecondaryIndexKey(secondaryIndex), types, parameters);
return secondaryIndexKey;
}
public Object updatePrimaryIndexKeyReadOnly(final Object primaryIndexKey, final boolean isReadOnly) {
return newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
Object[] parameters = new Object[]{isReadOnly, primaryIndexKey};
int[] types = new int[]{Types.BOOLEAN, JdbcTypeMapper.primitiveTypeToJdbcType(primaryIndexKey.getClass())};
lockPrimaryKeyForUpdate(primaryIndexKey);
doUpdate(sql.updateReadOnlyOfPrimaryIndexKey(partitionDimension), types, parameters);
return primaryIndexKey;
}
});
}
public Object updatePrimaryIndexKeyOfResourceId(final Resource resource, final Object resourceId, final Object newPrimaryIndexKey) {
return newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
Object[] parameters = new Object[]{newPrimaryIndexKey, resourceId};
int[] types = new int[]{
JdbcTypeMapper.primitiveTypeToJdbcType(newPrimaryIndexKey.getClass()),
resource.getColumnType()
};
lockResourceId(resource, resourceId);
doUpdate(sql.updateResourceId(resource), types, parameters);
return resourceId;
}
});
}
public void deletePrimaryIndexKey(final Object primaryIndexKey) {
for (Resource resource : getPartitionDimension().getResources()) {
if (!resource.isPartitioningResource())
for (Object resourceId : getResourceIdsOfPrimaryIndexKey(resource, primaryIndexKey)) {
deleteResourceId(resource, resourceId);
}
else
batch().deleteAllSecondaryIndexKeysOfResourceId(resource, primaryIndexKey);
}
newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
lockPrimaryKeyForUpdate(primaryIndexKey);
doUpdate(
sql.deletePrimaryIndexKey(partitionDimension),
new int[]{JdbcTypeMapper.primitiveTypeToJdbcType(primaryIndexKey.getClass())},
new Object[]{primaryIndexKey});
return primaryIndexKey;
}
});
}
public void deleteSecondaryIndexKey(final SecondaryIndex secondaryIndex, final Object secondaryIndexKey, final Object resourceId) {
newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
return deleteSecondaryIndexKeyNoTransaction(secondaryIndex, secondaryIndexKey, resourceId);
}
});
}
Object deleteSecondaryIndexKeyNoTransaction(final SecondaryIndex secondaryIndex, final Object secondaryIndexKey, final Object resourceId) {
Object[] parameters = new Object[]{secondaryIndexKey, resourceId};
int[] types = new int[]{
secondaryIndex.getColumnInfo().getColumnType(),
JdbcTypeMapper.primitiveTypeToJdbcType(resourceId.getClass())
};
lockSecondaryIndexKey(secondaryIndex, secondaryIndexKey, resourceId);
doUpdate(sql.deleteSingleSecondaryIndexKey(secondaryIndex), types, parameters);
return secondaryIndexKey;
}
@SuppressWarnings("unchecked")
public boolean doesPrimaryIndexKeyExist(Object primaryIndexKey) {
Collection count = doRead(sql.checkExistenceOfPrimaryKey(partitionDimension),
new Object[]{primaryIndexKey},
RowMappers.newTrueRowMapper());
return count.size() > 0;
}
public Collection<KeySemaphore> getKeySemamphoresOfPrimaryIndexKey(Object primaryIndexKey) {
return doRead(sql.selectKeySemaphoreOfPrimaryIndexKey(partitionDimension),
new Object[]{primaryIndexKey},
new KeySemaphoreRowMapper());
}
public boolean doesResourceIdExist(Resource resource, Object resourceId) {
if (resource.isPartitioningResource())
return doesPrimaryIndexKeyExist(resourceId);
Collection<Object> count =
doRead(
sql.checkExistenceOfResourceIndexSql(resource.getIdIndex()),
new Object[]{resourceId},
RowMappers.newTrueRowMapper());
return count.size() > 0;
}
public boolean doesSecondaryIndexKeyExist(SecondaryIndex secondaryIndex, Object secondaryIndexKey, Object resourceId) {
Collection<Object> count = doRead(
sql.checkExistenceOfSecondaryIndexSql(secondaryIndex),
new Object[]{secondaryIndexKey, resourceId},
RowMappers.newTrueRowMapper());
return count.size() > 0;
}
public Collection<KeySemaphore> getKeySemaphoresOfSecondaryIndexKey(SecondaryIndex secondaryIndex, Object secondaryIndexKey) {
return doRead(
sql.selectKeySemaphoresOfSecondaryIndexKey(secondaryIndex),
new Object[]{secondaryIndexKey},
new KeySemaphoreRowMapper());
}
@SuppressWarnings("unchecked")
public Collection<KeySemaphore> getKeySemaphoresOfResourceId(Resource resource, Object resourceId) {
return (Collection<KeySemaphore>) (resource.isPartitioningResource()
? getKeySemamphoresOfPrimaryIndexKey(resourceId)
: doRead(
sql.selectKeySemaphoresOfResourceId(resource),
new Object[]{resourceId},
new KeySemaphoreRowMapper()));
}
public Collection<Object> getPrimaryIndexKeysOfSecondaryIndexKey(SecondaryIndex secondaryIndex, Object secondaryIndexKey) {
return doRead(
sql.selectPrimaryIndexKeysOfSecondaryIndexKey(secondaryIndex),
new Object[]{secondaryIndexKey},
RowMappers.newObjectRowMapper(secondaryIndex.getResource().getPartitionDimension().getColumnType()));
}
public Collection<Object> getSecondaryIndexKeysOfPrimaryIndexKey(SecondaryIndex secondaryIndex, Object primaryIndexKey) {
return doRead(
sql.selectSecondaryIndexKeysOfPrimaryKey(secondaryIndex),
new Object[]{primaryIndexKey},
RowMappers.newObjectRowMapper(secondaryIndex.getColumnInfo().getColumnType()));
}
public void deleteResourceId(final Resource resource, final Object id) {
//todo:
// directory.batch().deleteAllSecondaryIndexKeysOfResourceId(getResource(resource), id);
newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
lockResourceId(resource, id);
doUpdate(sql.deleteResourceId(resource), new int[]{resource.getColumnType()}, new Object[]{id});
return id;
}
});
}
@SuppressWarnings("unchecked")
public Collection getResourceIdsOfSecondaryIndexKey(SecondaryIndex secondaryIndex, Object secondaryIndexKey) {
return doRead(
sql.selectResourceIdsOfSecondaryIndexKey(secondaryIndex),
new Object[]{secondaryIndexKey},
RowMappers.newObjectRowMapper(secondaryIndex.getResource().getColumnType()));
}
@SuppressWarnings("unchecked")
public Collection getResourceIdsOfPrimaryIndexKey(Resource resource, Object primaryIndexKey) {
return doRead(
sql.selectResourceIdsOfPrimaryIndexKey(resource.getIdIndex()),
new Object[]{primaryIndexKey},
RowMappers.newObjectRowMapper(resource.getColumnType()));
}
@SuppressWarnings("unchecked")
public Collection getSecondaryIndexKeysOfResourceId(SecondaryIndex secondaryIndex, Object id) {
return doRead(
sql.selectSecondaryIndexKeyOfResourceId(secondaryIndex),
new Object[]{id},
RowMappers.newObjectRowMapper(secondaryIndex.getColumnInfo().getColumnType()));
}
@SuppressWarnings("unchecked")
private <T> Collection<T> doRead(String sql, Object[] parameters, RowMapper mapper) {
try {
return (Collection<T>) getJdbcTemplate().query(sql, parameters, mapper);
} catch (EmptyResultDataAccessException e) {
throw new HiveKeyNotFoundException(String.format("Directory query returned no results. %s with parameters: %s", sql, parameters), e);
}
}
private void doUpdate(String sql, int[] types, Object[] parameters) {
getJdbcTemplate().update(Statements.newStmtCreatorFactory(sql, types).newPreparedStatementCreator(parameters));
}
public Object insertResourceId(final Resource resource, final Object id, final Object primaryIndexKey) {
return newTransaction().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus arg0) {
if (lockResourceId(resource, id))
doUpdate(sql.insertResourceId(resource),
new int[]{resource.getColumnType(), resource.getPartitionDimension().getColumnType()},
new Object[]{id, primaryIndexKey});
return id;
}
});
}
@SuppressWarnings("unchecked")
public Object getPrimaryIndexKeyOfResourceId(Resource resource, Object id) {
Collection keys = doRead(
sql.selectPrimaryIndexKeysOfResourceId(resource),
new Object[]{id},
RowMappers.newObjectRowMapper(resource.getPartitionDimension().getColumnType()));
if (keys.size() == 0)
throw new HiveKeyNotFoundException(String.format("Unable to find primary key for resource %s with id %s", resource.getName(), id), id);
else if (keys.size() > 1)
throw new DirectoryCorruptionException(String.format("Directory corruption: Resource %s with id %s is owned more than one primary key.", resource.getName(), id));
return Atom.getFirstOrNull(keys);
}
public Object insertSecondaryIndexKeys(Map<SecondaryIndex, Collection<Object>> secondaryIndexValueMap, Object resourceId) {
//todo: finish
return batch().insertSecondaryIndexKeys(secondaryIndexValueMap, resourceId);
}
public void deleteSecondaryIndexKeys(Map<SecondaryIndex, Collection<Object>> secondaryIndexValueMap, Object resourceId) {
//todo: complete this
batch().deleteSecondaryIndexKeys(secondaryIndexValueMap, resourceId);
}
public Unary<KeySemaphore, Integer> semaphoreToId() {
return new Unary<KeySemaphore, Integer>() {
public Integer f(KeySemaphore item) {
return item.getNodeId();
}
};
}
public Unary<KeySemaphore, Boolean> semaphoreToReadOnly() {
return new Unary<KeySemaphore, Boolean>() {
public Boolean f(KeySemaphore item) {
return !item.getStatus().equals(Status.writable);
}
};
}
public BatchIndexWriter batch() {
final DbDirectory d = this;
return cache.get(BatchIndexWriter.class, new Delay<BatchIndexWriter>() {
public BatchIndexWriter f() {
return new BatchIndexWriter(d);
}
});
}
private void setTransactionManager(TransactionTemplate transactionTemplate, final JdbcDaoSupport jdbcDaoSupport) {
transactionTemplate.setTransactionManager((DataSourceTransactionManager) cache.get(jdbcDaoSupport.getDataSource(), new Delay<DataSourceTransactionManager>() {
public DataSourceTransactionManager f() {
return new DataSourceTransactionManager(jdbcDaoSupport.getDataSource());
}
}));
}
private boolean lockPrimaryKeyForInsert(Object primaryIndexKey, Node node) {
return doRead(sql.selectCompositeKeyForUpdateLock(Schemas.getPrimaryIndexTableName(partitionDimension), "id", "node"),
new Object[]{primaryIndexKey, node.getId()},
RowMappers.newTrueRowMapper()).size() == 0;
}
private boolean lockPrimaryKeyForUpdate(Object primaryIndexKey) {
return doRead(sql.selectForUpdateLock(Schemas.getPrimaryIndexTableName(partitionDimension), "id"),
new Object[]{primaryIndexKey},
RowMappers.newTrueRowMapper()).size() == 0;
}
private boolean lockSecondaryIndexKey(SecondaryIndex secondaryIndex, Object secondaryIndexKey, Object resourceId) {
return doRead(sql.selectCompositeKeyForUpdateLock(Schemas.getSecondaryIndexTableName(secondaryIndex), "id", "pkey"),
new Object[]{secondaryIndexKey, resourceId},
RowMappers.newTrueRowMapper()).size() == 0;
}
private boolean lockResourceId(Resource resource, Object resourceId) {
return doRead(sql.selectForUpdateLock(Schemas.getResourceIndexTableName(resource), "id"),
new Object[]{resourceId},
RowMappers.newTrueRowMapper()).size() == 0;
}
public TransactionTemplate newTransaction() {
TransactionTemplate t = new TransactionTemplate();
setTransactionManager(t, this);
return t;
}
}