// Copyright 2017 JanusGraph Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.janusgraph.diskstorage.keycolumnvalue.inmemory; import com.google.common.base.Preconditions; import org.janusgraph.diskstorage.Entry; import org.janusgraph.diskstorage.EntryList; import org.janusgraph.diskstorage.StaticBuffer; import org.janusgraph.diskstorage.keycolumnvalue.*; import org.janusgraph.diskstorage.util.NoLock; import org.janusgraph.diskstorage.util.StaticArrayEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static org.janusgraph.graphdb.configuration.GraphDatabaseConfiguration.STORAGE_TRANSACTIONAL; /** * Implements a row in the in-memory implementation {@link InMemoryKeyColumnValueStore} which is comprised of * column-value pairs. This data is held in a sorted array for space and retrieval efficiency. * * @author Matthias Broecheler (me@matthiasb.com) */ class ColumnValueStore { private static final double SIZE_THRESHOLD = 0.66; private Data data; public ColumnValueStore() { data = new Data(new Entry[0], 0); } boolean isEmpty(StoreTransaction txh) { Lock lock = getLock(txh); lock.lock(); try { return data.isEmpty(); } finally { lock.unlock(); } } EntryList getSlice(KeySliceQuery query, StoreTransaction txh) { Lock lock = getLock(txh); lock.lock(); try { Data datacp = data; int start = datacp.getIndex(query.getSliceStart()); if (start < 0) start = (-start - 1); int end = datacp.getIndex(query.getSliceEnd()); if (end < 0) end = (-end - 1); if (start < end) { MemoryEntryList result = new MemoryEntryList(end - start); for (int i = start; i < end; i++) { if (query.hasLimit() && result.size() >= query.getLimit()) break; result.add(datacp.get(i)); } return result; } else { return EntryList.EMPTY_LIST; } } finally { lock.unlock(); } } private static class MemoryEntryList extends ArrayList<Entry> implements EntryList { public MemoryEntryList(int size) { super(size); } @Override public Iterator<Entry> reuseIterator() { return iterator(); } @Override public int getByteSize() { int size = 48; for (Entry e : this) { size += 8 + 16 + 8 + 8 + e.length(); } return size; } } synchronized void mutate(List<Entry> additions, List<StaticBuffer> deletions, StoreTransaction txh) { //Prepare data Entry[] add; if (!additions.isEmpty()) { add = new Entry[additions.size()]; int pos = 0; for (Entry e : additions) { add[pos] = e; pos++; } Arrays.sort(add); } else add = new Entry[0]; //Filter out deletions that are also added Entry[] del; if (!deletions.isEmpty()) { del = new Entry[deletions.size()]; int pos=0; for (StaticBuffer deletion : deletions) { Entry delEntry = StaticArrayEntry.of(deletion); if (Arrays.binarySearch(add,delEntry) >= 0) continue; del[pos++]=delEntry; } if (pos<deletions.size()) del = Arrays.copyOf(del,pos); Arrays.sort(del); } else del = new Entry[0]; Lock lock = getLock(txh); lock.lock(); try { Entry[] olddata = data.array; int oldsize = data.size; Entry[] newdata = new Entry[oldsize + add.length]; //Merge sort int i = 0, iold = 0, iadd = 0, idel = 0; while (iold < oldsize) { Entry e = olddata[iold]; iold++; //Compare with additions if (iadd < add.length) { int compare = e.compareTo(add[iadd]); if (compare >= 0) { e = add[iadd]; iadd++; //Skip duplicates while (iadd < add.length && e.equals(add[iadd])) iadd++; } if (compare > 0) iold--; } //Compare with deletions if (idel < del.length) { int compare = e.compareTo(del[idel]); if (compare == 0) e = null; if (compare >= 0) idel++; } if (e != null) { newdata[i] = e; i++; } } while (iadd < add.length) { newdata[i] = add[iadd]; i++; iadd++; } if (i * 1.0 / newdata.length < SIZE_THRESHOLD) { //shrink array to free space Entry[] tmpdata = newdata; newdata = new Entry[i]; System.arraycopy(tmpdata, 0, newdata, 0, i); } data = new Data(newdata, i); } finally { lock.unlock(); } } private ReentrantLock lock = null; private Lock getLock(StoreTransaction txh) { Boolean txOn = txh.getConfiguration().getCustomOption(STORAGE_TRANSACTIONAL); if (null != txOn && txOn) { if (lock == null) { synchronized (this) { if (lock == null) { lock = new ReentrantLock(); } } } return lock; } else return NoLock.INSTANCE; } private static class Data { final Entry[] array; final int size; Data(final Entry[] array, final int size) { Preconditions.checkArgument(size >= 0 && size <= array.length); assert isSorted(); this.array = array; this.size = size; } boolean isEmpty() { return size == 0; } int getIndex(StaticBuffer column) { return Arrays.binarySearch(array, 0, size, StaticArrayEntry.of(column)); } Entry get(int index) { return array[index]; } boolean isSorted() { for (int i = 1; i < size; i++) { if (!(array[i].compareTo(array[i - 1]) > 0)) return false; } return true; } } }