com.eventsourcing.h2.index.HashIndex Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eventsourcing-h2 Show documentation
Show all versions of eventsourcing-h2 Show documentation
Event capture and querying framework for Java
/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.h2.index;
import com.eventsourcing.Entity;
import com.eventsourcing.EntityHandle;
import com.eventsourcing.ResolvedEntityHandle;
import com.eventsourcing.index.AbstractHashingAttributeIndex;
import com.eventsourcing.index.Attribute;
import com.google.common.collect.Iterators;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Bytes;
import com.googlecode.cqengine.index.Index;
import com.googlecode.cqengine.index.support.*;
import com.googlecode.cqengine.persistence.support.ObjectSet;
import com.googlecode.cqengine.persistence.support.ObjectStore;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.option.QueryOptions;
import com.googlecode.cqengine.query.simple.Equal;
import com.googlecode.cqengine.query.simple.Has;
import com.googlecode.cqengine.resultset.ResultSet;
import lombok.Value;
import org.h2.mvstore.Cursor;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import java.nio.ByteBuffer;
import java.util.*;
public class HashIndex extends AbstractHashingAttributeIndex implements
KeyStatisticsAttributeIndex> {
protected static final int INDEX_RETRIEVAL_COST = 30;
private final MVStore store;
/**
* Map record structure:
*
*
*
* Key
* Value
*
*
*
* hash(attribute value)
* hash(object value)
* true
*
*
*
*/
private final MVMap map;
/**
* Map record structure:
*
*
*
* Key
* Value
*
*
*
* hash(attribute value)
* attribute value
*
*
*
*/
private final MVMap attrHashMap;
/**
* Map record structure:
*
*
*
* Key
* Value
*
*
*
* hash(object value)
* object value
*
*
*
*/
private final MVMap objHashMap;
/**
* Protected constructor, called by subclasses.
*
* @param attribute The attribute on which the index will be built
*/
protected HashIndex(MVStore store, Attribute attribute, HashFunction hashFunction) {
super(attribute, new HashSet>() {{
add(Equal.class);
add(Has.class);
}}, hashFunction);
this.store = store;
String classname = attribute.getEffectiveObjectType().getName();
map = store.openMap("hash_index_" + classname + "_" + attribute.getAttributeName());
attrHashMap = store.openMap("hash_index_attrhash_" + classname + "_" + attribute.getAttributeName());
objHashMap = store.openMap("hash_index_objhash_" + classname + "_" + attribute.getAttributeName());
}
public static HashIndex onAttribute(MVStore store, Attribute attribute) {
return onAttribute(store, attribute, Hashing.sha1());
}
public static HashIndex onAttribute(MVStore store, Attribute attribute,
HashFunction hashFunction) {
return new HashIndex<>(store, attribute, hashFunction);
}
private class KeyStatisticsCloseableIterable implements CloseableIterable> {
private final Iterator> iterator;
public KeyStatisticsCloseableIterable(Iterator> iterator) {
this.iterator = iterator;
}
@Override
public CloseableIterator> iterator() {
return new KeyStatisticsCloseableIterator(iterator);
}
private class KeyStatisticsCloseableIterator implements CloseableIterator> {
private final Iterator> iterator;
public KeyStatisticsCloseableIterator(Iterator> iterator) {
this.iterator = iterator;
}
@Override
public void close() {
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public KeyStatistics next() {
return iterator.next();
}
}
}
class CursorAttributeIterator implements CloseableIterator {
private final Cursor cursor;
public CursorAttributeIterator(Cursor cursor) {
this.cursor = cursor;
}
@Override
public void close() {
}
@Override
public boolean hasNext() {
return cursor.hasNext();
}
@Override
public A next() {
return attributeDeserializer.deserialize(ByteBuffer.wrap(attrHashMap.get(cursor.next())));
}
}
class Iterable implements CloseableIterable {
private final Cursor cursor;
public Iterable(Cursor cursor) {
this.cursor = cursor;
}
@Override
public CloseableIterator iterator() {
return new CursorAttributeIterator(cursor);
}
}
@Value
static class Entry {
private byte[] key;
private byte[] value;
private byte[] valueHash;
private byte[] attr;
private byte[] attrHash;
}
private byte[] encodeAttribute(A value) {
int size = attributeSerializer.size(value);
ByteBuffer serializedAttribute = ByteBuffer.allocate(size);
attributeSerializer.serialize(value, serializedAttribute);
return hashFunction.hashBytes(serializedAttribute.array()).asBytes();
}
private Entry encodeEntry(O object, A value) {
int attributeSize = attributeSerializer.size(value);
ByteBuffer serializedAttribute = ByteBuffer.allocate(attributeSize);
attributeSerializer.serialize(value, serializedAttribute);
int objectSize = objectSerializer.size(object);
ByteBuffer serializedObject = ByteBuffer.allocate(objectSize);
objectSerializer.serialize(object, serializedObject);
ByteBuffer buffer = ByteBuffer.allocate(hashSize * 2);
byte[] attrHash = hashFunction.hashBytes(serializedAttribute.array()).asBytes();
buffer.put(attrHash);
byte[] valueHash = hashFunction.hashBytes(serializedObject.array()).asBytes();
buffer.put(valueHash);
return new Entry(buffer.array(), serializedObject.array(), valueHash, serializedAttribute.array(), attrHash);
}
public A decodeKey(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
byte[] hash = new byte[hashSize];
buffer.get(hash);
return attributeDeserializer.deserialize(ByteBuffer.wrap(attrHashMap.get(hash)));
}
@Override
public CloseableIterable getDistinctKeys(QueryOptions queryOptions) {
return new Iterable(attrHashMap.cursor(attrHashMap.firstKey()));
}
@Override
public Integer getCountForKey(A key, QueryOptions queryOptions) {
byte[] attr = encodeAttribute(key);
Cursor cursor = map.cursor(map.ceilingKey(attr));
int i = 0;
while (cursor.hasNext() && Bytes.indexOf(cursor.next(), attr) == 0) {
i++;
}
return i;
}
@Override
public Integer getCountOfDistinctKeys(QueryOptions queryOptions) {
return attrHashMap.size();
}
@Override
public CloseableIterable> getStatisticsForDistinctKeys(QueryOptions queryOptions) {
List> statistics = new ArrayList<>();
for (A key : getDistinctKeys(queryOptions)) {
statistics.add(new KeyStatistics<>(key, getCountForKey(key, queryOptions)));
}
Iterator> iterator = statistics.iterator();
return new KeyStatisticsCloseableIterable(iterator);
}
@Override
public CloseableIterable>> getKeysAndValues(QueryOptions queryOptions) {
return null;
}
@Override
public boolean isMutable() {
return !map.isReadOnly();
}
@Override
public boolean isQuantized() {
return false;
}
@Override
public ResultSet> retrieve(Query> query, QueryOptions queryOptions) {
Class> queryClass = query.getClass();
if (queryClass.equals(Equal.class)) {
final Equal, A> equal = (Equal, A>) query;
byte[] attr = encodeAttribute(equal.getValue());
byte[] from = map.ceilingKey(attr);
return new ResultSet>() {
@Override
public Iterator> iterator() {
boolean empty = Bytes.indexOf(from, attr) != 0;
Cursor cursor = map.cursor(from);
if (empty) {
return Collections.>emptyList().iterator();
}
return new CursorIterator(cursor, attr);
}
@Override
public boolean contains(EntityHandle object) {
Entry entry = encodeEntry(object.get(), equal.getValue());
return objHashMap.containsKey(entry.getValueHash());
}
@Override
public boolean matches(EntityHandle object) {
return equal.matches(object, queryOptions);
}
@Override
public Query> getQuery() {
return equal;
}
@Override
public QueryOptions getQueryOptions() {
return queryOptions;
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
return Iterators.size(iterator());
}
@Override
public int size() {
return Iterators.size(iterator());
}
@Override
public void close() {
}
};
} else if (queryClass.equals(Has.class)) {
final Has, A> has = (Has, A>) query;
byte[] from = map.firstKey();
return new ResultSet>() {
@Override
public Iterator> iterator() {
Cursor cursor = map.cursor(from);
return new CursorIterator(cursor, new byte[]{});
}
@Override
public boolean contains(EntityHandle object) {
ByteBuffer buffer = ByteBuffer.allocate(objectSerializer.size(object.get()));
objectSerializer.serialize(object.get(), buffer);
return objHashMap.containsKey(hashFunction.hashBytes(buffer.array()).asBytes());
}
@Override
public boolean matches(EntityHandle object) {
return has.matches(object, queryOptions);
}
@Override
public Query> getQuery() {
return has;
}
@Override
public QueryOptions getQueryOptions() {
return queryOptions;
}
@Override
public int getRetrievalCost() {
return INDEX_RETRIEVAL_COST;
}
@Override
public int getMergeCost() {
return Iterators.size(iterator());
}
@Override
public int size() {
return Iterators.size(iterator());
}
@Override
public void close() {
}
};
} else {
throw new IllegalArgumentException("Unsupported query: " + query);
}
}
@Override
public Index> getEffectiveIndex() {
return this;
}
@Override
public boolean addAll(ObjectSet> objects, QueryOptions queryOptions) {
try (CloseableIterator> iterator = objects.iterator()) {
while (iterator.hasNext()) {
addObject(queryOptions, iterator.next());
}
}
return true;
}
public boolean addAll(ObjectStore> objects, QueryOptions queryOptions) {
CloseableIterator> iterator = objects.iterator(queryOptions);
while (iterator.hasNext()) {
EntityHandle object = iterator.next();
addObject(queryOptions, object);
}
return true;
}
private void addObject(QueryOptions queryOptions, EntityHandle object) {
for (A value : attribute.getValues(object, queryOptions)) {
if (value != null) { // Don't index null attribute values
Entry entry = encodeEntry(object.get(), value);
map.put(entry.getKey(), true);
attrHashMap.putIfAbsent(entry.getAttrHash(), entry.getAttr());
objHashMap.putIfAbsent(entry.getValueHash(), entry.getValue());
}
}
}
@Override
public boolean removeAll(ObjectSet> objects, QueryOptions queryOptions) {
try (CloseableIterator> iterator = objects.iterator()) {
while (iterator.hasNext()) {
EntityHandle object = iterator.next();
for (A value : attribute.getValues(object, queryOptions)) {
Entry entry = encodeEntry(object.get(), value);
map.remove(entry.getKey());
}
}
}
return true;
}
@Override
public void clear(QueryOptions queryOptions) {
map.clear();
}
@Override
public void init(ObjectStore> objectStore, QueryOptions queryOptions) {
addAll(objectStore, queryOptions);
}
private class CursorIterator implements Iterator> {
private final Cursor cursor;
private final byte[] attr;
private byte[] next;
public CursorIterator(Cursor cursor, byte[] attr) {
this.cursor = cursor;
this.attr = attr;
}
@Override
public boolean hasNext() {
if (cursor.hasNext()) {
next = cursor.next();
return Bytes.indexOf(next, attr) == 0;
} else {
return false;
}
}
@Override
public EntityHandle next() {
ByteBuffer buffer = ByteBuffer.wrap(next);
buffer.position(hashSize); // skip attribute hash
byte[] hash = new byte[hashSize];
buffer.get(hash);
return new ResolvedEntityHandle<>(objectDeserializer.deserialize(ByteBuffer.wrap(objHashMap.get(hash))));
}
}
}