Skip to content

Commit

Permalink
Added support for non-unique Multi-Indices
Browse files Browse the repository at this point in the history
A single value can be assigned to a set of index keys.
Each index key alone can be used to access the object
  • Loading branch information
JanWiemer committed Jan 17, 2024
1 parent 4752250 commit 3a64bd0
Show file tree
Hide file tree
Showing 9 changed files with 591 additions and 213 deletions.
63 changes: 63 additions & 0 deletions src/main/java/org/jacis/index/AbstractJacisMultiIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.jacis.index;

import org.jacis.JacisApi;

import java.util.Set;
import java.util.function.Function;

/**
* Abstract base class for Jacis Indices where one value object can have multiple index keys.
*
* @param <IK> Index key type for this unique index
* @param <K> Key type of the store entry
* @param <TV> Type of the objects in the transaction view. This is the type visible from the outside.
* @author Jan Wiemer
*/
@JacisApi
public abstract class AbstractJacisMultiIndex<IK, K, TV> {

/** Name of the index used to register it at the store. The index names have to be unique for one store. */
protected final String indexName;
/** Reference to the index registry storing all indices registered for a store. */
protected final JacisIndexRegistry<K, TV> indexRegistry;
/** Function defining how to compute the set of index keys from the value stored in the store. */
protected final Function<TV, Set<IK>> indexKeyFunction;

AbstractJacisMultiIndex(String indexName, Function<TV, Set<IK>> indexKeyFunction, JacisIndexRegistry<K, TV> indexRegistry) {
this.indexName = indexName;
this.indexKeyFunction = indexKeyFunction;
this.indexRegistry = indexRegistry;
}

/** @return the name of the index (the name used to register the index during creation). */
public String getIndexName() {
return indexName;
}

/** @return The function to compute the set of the index keys from the stored value. */
Function<TV, Set<IK>> getIndexKeyFunction() {
return indexKeyFunction;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + indexName + ")";
}

@Override
public int hashCode() {
return indexName.hashCode();
}

@SuppressWarnings("unchecked")
@Override
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (!this.getClass().equals(that.getClass())) {
return false;
}
return this.indexName.equals(((AbstractJacisMultiIndex<IK, K, TV>) that).indexName);
}
}
177 changes: 162 additions & 15 deletions src/main/java/org/jacis/index/JacisIndexRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ public class JacisIndexRegistry<K, TV> implements JacisModificationListener<K, T
/** The data maps for the non-unique indices. For each index the data map contains the mapping from the index key to the primary key of the object in the store. */
private final Map<String, Map<Object, Set<K>>> nonUniqueIndexDataMap = new HashMap<>();

/** Map containing the non-unique multi-index definitions for each registered unique index */
private final ConcurrentHashMap<String, JacisNonUniqueMultiIndex<?, K, TV>> nonUniqueMultiIndexDefinitionMap = new ConcurrentHashMap<>();
/** The data maps for the non-unique multi-indices. For each index the data map contains the mapping from the index key to the primary key of the object in the store. */
private final Map<String, Map<Object, Set<K>>> nonUniqueMultiIndexDataMap = new HashMap<>();

/** Map containing the unique index definitions for each registered unique index */
private final ConcurrentHashMap<String, JacisUniqueIndex<?, K, TV>> uniqueIndexDefinitionMap = new ConcurrentHashMap<>();
/** The data maps for the unique indices. For each index the data map contains the mapping from the index key to the primary keys of the objects in the store. */
private final Map<String, Map<Object, K>> uniqueIndexDataMap = new HashMap<>();

/**
* The lock maps for the unique indices. For each index the lock map contains the mapping from the index key to the lock information for this index.
* The lock information (see {@link IndexLock}) indicates if the index value is locked for another prepared transaction,
Expand All @@ -54,24 +60,47 @@ public JacisIndexRegistry(JacisStoreImpl<K, TV, ?> store, boolean checkConsisten
public void clearIndices() {
store.executeAtomic(() -> {
nonUniqueIndexDataMap.values().forEach(Map::clear);
nonUniqueMultiIndexDataMap.values().forEach(Map::clear);
uniqueIndexDataMap.values().forEach(Map::clear);
uniqueIndexLockMap.clear();
});
}

public boolean hasAnyRegisteredIndex() {
boolean isEmpty = uniqueIndexDefinitionMap.isEmpty() && nonUniqueIndexDefinitionMap.isEmpty();
boolean isEmpty = uniqueIndexDefinitionMap.isEmpty() //
&& nonUniqueIndexDefinitionMap.isEmpty() //
&& nonUniqueMultiIndexDefinitionMap.isEmpty();
return !isEmpty;
}

public Collection<JacisNonUniqueIndex<?, K, TV>> getNonUniqueIndexDefinitions() {
return nonUniqueIndexDefinitionMap.values();
}

public Collection<JacisNonUniqueMultiIndex<?, K, TV>> getNonUniqueMultiIndexDefinitions() {
return nonUniqueMultiIndexDefinitionMap.values();
}

public Collection<JacisUniqueIndex<?, K, TV>> getUniqueIndexDefinitions() {
return uniqueIndexDefinitionMap.values();
}

@SuppressWarnings("unchecked")
public <IK> JacisNonUniqueIndex<IK, K, TV> getNonUniqueIndex(String indexName) {
return (JacisNonUniqueIndex<IK, K, TV>) nonUniqueIndexDefinitionMap.get(indexName);
}

@SuppressWarnings("unchecked")
public <IK> JacisNonUniqueMultiIndex<IK, K, TV> getNonUniqueMultiIndex(String indexName) {
return (JacisNonUniqueMultiIndex<IK, K, TV>) nonUniqueMultiIndexDefinitionMap.get(indexName);
}

@SuppressWarnings("unchecked")
public <IK> JacisUniqueIndex<IK, K, TV> getUniqueIndex(String indexName) {
return (JacisUniqueIndex<IK, K, TV>) uniqueIndexDefinitionMap.get(indexName);
}


@SuppressWarnings("unchecked")
public <IK> JacisNonUniqueIndex<IK, K, TV> createNonUniqueIndex(String indexName, Function<TV, IK> indexKeyFunction) {
JacisNonUniqueIndex<IK, K, TV> idx = (JacisNonUniqueIndex<IK, K, TV>) nonUniqueIndexDefinitionMap.compute(indexName, (k, v) -> {
Expand All @@ -86,17 +115,16 @@ public <IK> JacisNonUniqueIndex<IK, K, TV> createNonUniqueIndex(String indexName
}

@SuppressWarnings("unchecked")
public <IK> JacisNonUniqueIndex<IK, K, TV> getNonUniqueIndex(String indexName) {
return (JacisNonUniqueIndex<IK, K, TV>) nonUniqueIndexDefinitionMap.get(indexName);
}

private <IK> void initializeNonUniqueIndex(JacisNonUniqueIndex<IK, K, TV> idx) {
String indexName = idx.getIndexName();
store.executeAtomic(() -> {
checkRegisterModificationListener();
nonUniqueIndexDataMap.put(indexName, new ConcurrentHashMap<>());
store.streamKeyValuePairsReadOnly(null).forEach(pair -> trackModificationAtNonUniqueIndex(idx, pair.getKey(), null, pair.getVal()));
public <IK> JacisNonUniqueMultiIndex<IK, K, TV> createNonUniqueMultiIndex(String indexName, Function<TV, Set<IK>> indexKeyFunction) {
JacisNonUniqueMultiIndex<IK, K, TV> idx = (JacisNonUniqueMultiIndex<IK, K, TV>) nonUniqueMultiIndexDefinitionMap.compute(indexName, (k, v) -> {
if (v != null) {
throw new IllegalStateException("An index with the name " + k + " is already registered at the store " + store);
} else {
return new JacisNonUniqueMultiIndex<>(indexName, indexKeyFunction, this);
}
});
initializeNonUniqueMultiIndex(idx);
return Objects.requireNonNull(idx);
}

@SuppressWarnings("unchecked")
Expand All @@ -112,9 +140,22 @@ public <IK> JacisUniqueIndex<IK, K, TV> createUniqueIndex(String indexName, Func
return Objects.requireNonNull(idx);
}

@SuppressWarnings("unchecked")
public <IK> JacisUniqueIndex<IK, K, TV> getUniqueIndex(String indexName) {
return (JacisUniqueIndex<IK, K, TV>) uniqueIndexDefinitionMap.get(indexName);
private <IK> void initializeNonUniqueMultiIndex(JacisNonUniqueMultiIndex<IK, K, TV> idx) {
String indexName = idx.getIndexName();
store.executeAtomic(() -> {
checkRegisterModificationListener();
nonUniqueMultiIndexDataMap.put(indexName, new ConcurrentHashMap<>());
store.streamKeyValuePairsReadOnly(null).forEach(pair -> trackModificationAtNonUniqueMultiIndex(idx, pair.getKey(), null, pair.getVal()));
});
}

private <IK> void initializeNonUniqueIndex(JacisNonUniqueIndex<IK, K, TV> idx) {
String indexName = idx.getIndexName();
store.executeAtomic(() -> {
checkRegisterModificationListener();
nonUniqueIndexDataMap.put(indexName, new ConcurrentHashMap<>());
store.streamKeyValuePairsReadOnly(null).forEach(pair -> trackModificationAtNonUniqueIndex(idx, pair.getKey(), null, pair.getVal()));
});
}

private <IK> void initializeUniqueIndex(JacisUniqueIndex<IK, K, TV> idx) {
Expand All @@ -128,7 +169,7 @@ private <IK> void initializeUniqueIndex(JacisUniqueIndex<IK, K, TV> idx) {
}

private void checkRegisterModificationListener() {
if (nonUniqueIndexDataMap.isEmpty() && uniqueIndexDataMap.isEmpty()) {
if (nonUniqueIndexDataMap.isEmpty() && nonUniqueMultiIndexDataMap.isEmpty() && uniqueIndexDataMap.isEmpty()) {
store.registerModificationListener(this); // first index created -> register at store
}
}
Expand All @@ -141,6 +182,9 @@ public void onModification(K key, TV oldValue, TV newValue, JacisTransactionHand
for (JacisNonUniqueIndex<?, K, TV> idx : nonUniqueIndexDefinitionMap.values()) {
trackModificationAtNonUniqueIndex(idx, key, oldValue, newValue);
}
for (JacisNonUniqueMultiIndex<?, K, TV> idx : nonUniqueMultiIndexDefinitionMap.values()) {
trackModificationAtNonUniqueMultiIndex(idx, key, oldValue, newValue);
}
}

private void trackModificationAtUniqueIndex(JacisUniqueIndex<?, K, TV> idx, K key, TV oldValue, TV newValue) {
Expand Down Expand Up @@ -179,6 +223,29 @@ private void trackModificationAtNonUniqueIndex(JacisNonUniqueIndex<?, K, TV> idx
}
}

private void trackModificationAtNonUniqueMultiIndex(JacisNonUniqueMultiIndex<?, K, TV> idx, K key, TV oldValue, TV newValue) {
Function<TV, ?> indexKeyFunction = idx.getIndexKeyFunction();
Map<Object, Set<K>> indexMap = nonUniqueMultiIndexDataMap.get(idx.getIndexName());
Object oldIndexKeyObject = oldValue == null ? null : indexKeyFunction.apply(oldValue);
Object newIndexKeyObject = newValue == null ? null : indexKeyFunction.apply(newValue);
Set<?> oldIndexKeySet = oldIndexKeyObject == null ? Collections.emptySet() : (Set<?>) oldIndexKeyObject;
Set<?> newIndexKeySet = newIndexKeyObject == null ? Collections.emptySet() : (Set<?>) newIndexKeyObject;
for (Object oldIndexKey : oldIndexKeySet) {
if (oldIndexKey != null) {
Set<K> primaryKeySet = indexMap.get(oldIndexKey);
if (primaryKeySet != null) {
primaryKeySet.remove(key);
}
}
}
for (Object newIndexKey : newIndexKeySet) {
if (newIndexKey != null) {
Set<K> primaryKeySet = indexMap.computeIfAbsent(newIndexKey, k -> new HashSet<>());
primaryKeySet.add(key);
}
}
}

@SuppressWarnings("unchecked")
void checkUniqueIndexProperty(JacisUniqueIndex<?, K, TV> idx, Object newIndexKey, K primaryKey, boolean considerTx) {
K oldPrimaryKeyForIdxKey;
Expand All @@ -198,6 +265,10 @@ void checkUniqueIndexProperty(JacisUniqueIndex<?, K, TV> idx, Object newIndexKey
}
}

//----------------------------------------------------------------------------------------------------------
//----- Access methods for NON-UNIQUE INDEX
//----------------------------------------------------------------------------------------------------------

<IK> Set<K> getFromNonUniqueIndexPrimaryKeys(JacisNonUniqueIndex<IK, K, TV> index, IK indexKey) {
JacisIndexRegistryTxView<K, TV> regTxView = store.getIndexRegistryTransactionView(); // null if no TX
String indexName = index.getIndexName();
Expand Down Expand Up @@ -262,6 +333,78 @@ <IK> Collection<TV> multiGetFromNonUniqueIndexReadOnly(JacisNonUniqueIndex<IK, K
return res;
}

//----------------------------------------------------------------------------------------------------------
//----- Access methods for NON-UNIQUE MULTI-INDEX
//----------------------------------------------------------------------------------------------------------

<IK> Set<K> getFromNonUniqueMultiIndexPrimaryKeys(JacisNonUniqueMultiIndex<IK, K, TV> index, IK indexKey) {
JacisIndexRegistryTxView<K, TV> regTxView = store.getIndexRegistryTransactionView(); // null if no TX
String indexName = index.getIndexName();
Map<Object, Set<K>> indexMap = nonUniqueMultiIndexDataMap.get(indexName);
if (regTxView != null) {
Set<K> add = regTxView.getPrimaryKeysAddedForNonUniqueIndex(indexName, indexKey);
Set<K> del = regTxView.getPrimaryKeysDeletedForNonUniqueIndex(indexName, indexKey);
if (!add.isEmpty() || !del.isEmpty()) {
Set<K> res = indexMap.getOrDefault(indexKey, new HashSet<>());
res.removeAll(del);
res.addAll(add);
return res;
}
}
return indexMap.getOrDefault(indexKey, Collections.emptySet());
}

<IK> Set<K> multiGetFromNonUniqueMultiIndexPrimaryKeys(JacisNonUniqueMultiIndex<IK, K, TV> index, Collection<IK> indexKeys) {
if (indexKeys == null || indexKeys.isEmpty()) {
return Collections.emptySet();
}
Set<K> res = new HashSet<>();
for (IK indexKey : indexKeys) {
res.addAll(getFromNonUniqueMultiIndexPrimaryKeys(index, indexKey));
}
return res;
}

<IK> Collection<TV> getFromNonUniqueMultiIndex(JacisNonUniqueMultiIndex<IK, K, TV> index, IK indexKey) {
Set<K> primaryKeys = getFromNonUniqueMultiIndexPrimaryKeys(index, indexKey);
Collection<TV> res = new ArrayList<>(primaryKeys.size());
for (K primaryKey : primaryKeys) {
res.add(store.get(primaryKey));
}
return res;
}

<IK> Collection<TV> multiGetFromNonUniqueMultiIndex(JacisNonUniqueMultiIndex<IK, K, TV> index, Collection<IK> indexKeys) {
Set<K> primaryKeys = multiGetFromNonUniqueMultiIndexPrimaryKeys(index, indexKeys);
Collection<TV> res = new ArrayList<>(primaryKeys.size());
for (K primaryKey : primaryKeys) {
res.add(store.get(primaryKey));
}
return res;
}

<IK> Collection<TV> getFromNonUniqueMultiIndexReadOnly(JacisNonUniqueMultiIndex<IK, K, TV> index, IK indexKey) {
Set<K> primaryKeys = getFromNonUniqueMultiIndexPrimaryKeys(index, indexKey);
Collection<TV> res = new ArrayList<>(primaryKeys.size());
for (K primaryKey : primaryKeys) {
res.add(store.getReadOnly(primaryKey));
}
return res;
}

<IK> Collection<TV> multiGetFromNonUniqueMultiIndexReadOnly(JacisNonUniqueMultiIndex<IK, K, TV> index, Collection<IK> indexKeys) {
Set<K> primaryKeys = multiGetFromNonUniqueMultiIndexPrimaryKeys(index, indexKeys);
Collection<TV> res = new ArrayList<>(primaryKeys.size());
for (K primaryKey : primaryKeys) {
res.add(store.getReadOnly(primaryKey));
}
return res;
}

//----------------------------------------------------------------------------------------------------------
//----- Access methods for UNIQUE INDEX
//----------------------------------------------------------------------------------------------------------

@java.lang.SuppressWarnings("java:S2789")
<IK> K getFromUniqueIndexPrimaryKey(JacisUniqueIndex<IK, K, TV> index, IK indexKey) {
JacisIndexRegistryTxView<K, TV> regTxView = store.getIndexRegistryTransactionView(); // null if no TX
Expand Down Expand Up @@ -316,6 +459,10 @@ <IK> Collection<TV> multiGetFromUniqueIndexReadOnly(JacisUniqueIndex<IK, K, TV>
return res;
}

//----------------------------------------------------------------------------------------------------------
//----- Lock methods for UNIQUE INDEX
//----------------------------------------------------------------------------------------------------------

public void lockUniqueIndexKeysForTx(JacisTransactionHandle txHandle) {
if (!uniqueIndexDefinitionMap.isEmpty()) {
String txId = txHandle.getTxId();
Expand Down
Loading

0 comments on commit 3a64bd0

Please sign in to comment.