All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.eventsourcing.h2.index.HashIndex Maven / Gradle / Ivy

The newest version!
/**
 * 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 journal structure:
     * 

*

* * * * * * * * * * * *
KeyValue
hash(attribute value)hash(object value)true
*/ private final MVMap map; /** * Map journal structure: *

*

* * * * * * * * * * *
KeyValue
hash(attribute value)attribute value
*/ private final MVMap attrHashMap; /** * Map journal structure: *

*

* * * * * * * * * * *
KeyValue
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(attrTypeHandler, 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(attrTypeHandler, value); ByteBuffer serializedAttribute = ByteBuffer.allocate(size); attributeSerializer.serialize(attrTypeHandler, value, serializedAttribute); return hashFunction.hashBytes(serializedAttribute.array()).asBytes(); } private Entry encodeEntry(O object, A value) { int attributeSize = attributeSerializer.size(attrTypeHandler, value); ByteBuffer serializedAttribute = ByteBuffer.allocate(attributeSize); attributeSerializer.serialize(attrTypeHandler, 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(attrTypeHandler, 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)))); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy