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

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

There is a newer version: 0.4.6
Show 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.UnmodifiableIterator;
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.CloseableIterator;
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.resultset.ResultSet;
import lombok.Value;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;

import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class UniqueIndex extends AbstractHashingAttributeIndex {


    protected static final int INDEX_RETRIEVAL_COST = 25;

    private final MVStore store;

    /**
     * Map record structure:
     * 

*

* * * * * * * * * * * *
KeyValue
hash(attribute value)attribute valueobject value
*/ private final MVMap map; public UniqueIndex(MVStore store, Attribute attribute, HashFunction hashFunction) { super(attribute, new HashSet>() {{ add(Equal.class); }}, hashFunction); this.store = store; String classname = attribute.getObjectType().getName(); map = store.openMap("unique_index_" + classname + "_" + attribute.getAttributeName()); } public static UniqueIndex onAttribute(MVStore store, Attribute attribute) { return onAttribute(store, attribute, Hashing.sha1()); } public static UniqueIndex onAttribute(MVStore store, Attribute attribute, HashFunction hashFunction) { return new UniqueIndex<>(store, attribute, hashFunction); } @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[] val = map.get(encodeKey(equal.getValue())); final EntityHandle obj = val == null ? null : new ResolvedEntityHandle(decodeVal(val).getObject()); return new ResultSet>() { @Override public Iterator> iterator() { return new UnmodifiableIterator>() { boolean hasNext = (obj != null); @Override public boolean hasNext() { return this.hasNext; } @Override public EntityHandle next() { this.hasNext = false; return obj; } }; } @Override public boolean contains(EntityHandle object) { return (object != null && obj != null && object.equals(obj)); } @Override public boolean matches(EntityHandle object) { return query.matches(object, queryOptions); } @Override public Query> getQuery() { return query; } @Override public QueryOptions getQueryOptions() { return queryOptions; } @Override public int getRetrievalCost() { return INDEX_RETRIEVAL_COST; } @Override public int getMergeCost() { return obj == null ? 0 : 1; } @Override public int size() { return obj == null ? 0 : 1; } @Override public void close() { } }; } throw new IllegalArgumentException("Unsupported query: " + query); } @Override public Index> getEffectiveIndex() { return this; } @Value static class Entry { private byte[] key; private byte[] value; private byte[] attr; private byte[] attrHash; } private byte[] encodeKey(A value) { int attributeSize = attributeSerializer.size(value); ByteBuffer serializedAttribute = ByteBuffer.allocate(attributeSize); 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); byte[] attrHash = hashFunction.hashBytes(serializedAttribute.array()).asBytes(); return new Entry(attrHash, Bytes.concat(serializedAttribute.array(), serializedObject.array()), serializedAttribute.array(), attrHash); } @Value static class Val { private A attr; private O object; } public Val decodeVal(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); A attr = attributeDeserializer.deserialize(attrTypeHandler, buffer); O obj = objectDeserializer.deserialize(buffer); return new Val<>(attr, obj); } @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); if (map.containsKey(entry.getKey()) && !decodeVal(map.get(entry.getKey())).getObject().equals(object)) { throw new com.googlecode.cqengine.index.unique.UniqueIndex.UniqueConstraintViolatedException( "The application has attempted to add a duplicate object to the UniqueIndex on attribute '" + attribute.getAttributeName() + "', potentially causing inconsistencies between indexes. " + "UniqueIndex should not be used with attributes which do not uniquely identify objects. " + "Problematic attribute value: '" + decodeVal(map.get(entry.getKey())) .getAttr() + "', " + "problematic duplicate object: " + object); } map.put(entry.getKey(), 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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy