
com.hazelcast.query.impl.OrderedIndexStore Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.query.impl;
import com.hazelcast.core.TypeConverter;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.query.Predicate;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.stream.Stream;
import static com.hazelcast.query.impl.AbstractIndex.NULL;
import static com.hazelcast.query.impl.CompositeValue.POSITIVE_INFINITY;
import static java.util.Collections.emptyIterator;
import static java.util.Collections.emptySet;
/**
* Store indexes rankly.
*/
@SuppressWarnings("rawtypes")
public class OrderedIndexStore extends BaseSingleValueIndexStore {
public static final Comparator DATA_COMPARATOR = new DataComparator();
public static final Comparator SPECIAL_AWARE_COMPARATOR = (left, right) -> {
// compare to explicit instances of special Comparables to avoid infinite loop
// NEGATIVE_INFINITY should not be used in the index or queries
// - the same result can be achieved by inclusive NULL or null.
if (right == NULL) {
return -NULL.compareTo(left);
} else if (right == POSITIVE_INFINITY) {
return -POSITIVE_INFINITY.compareTo(left);
} else if (left == null && right == null) {
return 0;
} else if (left != null && right != null) {
return Comparables.compare(left, right);
} else if (left == null) {
return -1;
} else {
return 1;
}
};
private final ConcurrentSkipListMap> recordMap =
new ConcurrentSkipListMap<>(SPECIAL_AWARE_COMPARATOR);
private final IndexFunctor addFunctor;
private final IndexFunctor removeFunctor;
public OrderedIndexStore(IndexCopyBehavior copyOn) {
super(copyOn, true);
assert copyOn != null;
if (copyOn == IndexCopyBehavior.COPY_ON_WRITE) {
addFunctor = new CopyOnWriteAddFunctor();
removeFunctor = new CopyOnWriteRemoveFunctor();
} else {
addFunctor = new AddFunctor();
removeFunctor = new RemoveFunctor();
}
}
@Override
Object insertInternal(Comparable value, QueryableEntry record) {
return addFunctor.invoke(value, record);
}
@Override
Object removeInternal(Comparable value, Data recordKey) {
return removeFunctor.invoke(value, recordKey);
}
@Override
public Comparable canonicalizeQueryArgumentScalar(Comparable value) {
// We still need to canonicalize query arguments for ordered indexes to
// support InPredicate queries.
return Comparables.canonicalizeForHashLookup(value);
}
@Override
public Comparable canonicalizeScalarForStorage(Comparable value) {
// Returning the original value since ordered indexes are not supporting
// hash lookups on their stored values, so there is no need in providing
// canonical representations.
return value;
}
@Override
public void clear() {
takeWriteLock();
try {
recordMap.clear();
} finally {
releaseWriteLock();
}
}
@Override
public boolean isEvaluateOnly() {
return false;
}
@Override
public boolean canEvaluate(Class extends Predicate> predicateClass) {
return false;
}
@Override
public Set evaluate(Predicate predicate, TypeConverter converter) {
throw new UnsupportedOperationException();
}
@Override
public Iterator getSqlRecordIterator(boolean descending) {
return new IteratorFromBatch(getSqlRecordIteratorBatch(descending));
}
@Override
public Iterator getSqlRecordIterator(@Nonnull Comparable value) {
return new IteratorFromBatch(getSqlRecordIteratorBatch(value, false));
}
@Override
public Iterator getSqlRecordIterator(Comparison comparison, Comparable searchedValue, boolean descending) {
return new IteratorFromBatch(getSqlRecordIteratorBatch(comparison, searchedValue, descending));
}
@Override
public Iterator getSqlRecordIterator(
Comparable from,
boolean fromInclusive,
Comparable to,
boolean toInclusive,
boolean descending
) {
return new IteratorFromBatch(getSqlRecordIteratorBatch(from, fromInclusive, to, toInclusive, descending));
}
@Override
public Iterator getSqlRecordIteratorBatch(@Nonnull Comparable value, boolean descending) {
return getSqlRecordIteratorBatch(value, descending, null);
}
@Override
public Iterator getSqlRecordIteratorBatch(
@Nonnull Comparable value,
boolean descending,
Data lastEntryKeyData
) {
var entries = recordMap.get(value);
if (entries == null) {
return emptyIterator();
}
entries = descending ? entries.descendingMap() : entries;
if (lastEntryKeyData != null) {
entries = entries.tailMap(lastEntryKeyData, false);
}
return Stream.of(
new IndexKeyEntries(
value,
entries.values().iterator()
)
).iterator();
}
@Override
public Iterator getSqlRecordIteratorBatch(boolean descending) {
if (descending) {
return recordMap.descendingMap().entrySet()
.stream()
.map((Entry> es) ->
new IndexKeyEntries(es.getKey(), es.getValue().descendingMap().values().iterator()))
.iterator();
} else {
return recordMap.entrySet()
.stream()
.map((Entry> es) ->
new IndexKeyEntries(es.getKey(), es.getValue().values().iterator()))
.iterator();
}
}
@Override
public Iterator getSqlRecordIteratorBatch(
@Nonnull Comparison comparison,
@Nonnull Comparable searchedValue,
boolean descending
) {
return getSqlRecordIteratorBatch(comparison, searchedValue, descending, null);
}
@Override
public Iterator getSqlRecordIteratorBatch(
@Nonnull Comparison comparison,
@Nonnull Comparable searchedValue,
boolean descending,
Data lastEntryKeyData
) {
switch (comparison) {
case LESS:
return getSqlRecordIteratorBatch(NULL, false, searchedValue, false, descending, lastEntryKeyData);
case LESS_OR_EQUAL:
return getSqlRecordIteratorBatch(NULL, false, searchedValue, true, descending, lastEntryKeyData);
case GREATER:
return getSqlRecordIteratorBatch(searchedValue, false, POSITIVE_INFINITY, true, descending, lastEntryKeyData);
case GREATER_OR_EQUAL:
return getSqlRecordIteratorBatch(searchedValue, true, POSITIVE_INFINITY, true, descending, lastEntryKeyData);
default:
throw new IllegalArgumentException("Unrecognized comparison: " + comparison);
}
}
@Override
@SuppressWarnings("checkstyle:NPathComplexity")
public Iterator getSqlRecordIteratorBatch(
@Nonnull Comparable from,
boolean fromInclusive,
@Nonnull Comparable to,
boolean toInclusive,
boolean descending
) {
return getSqlRecordIteratorBatch(from, fromInclusive, to, toInclusive, descending, null);
}
@Override
@SuppressWarnings({"checkstyle:NPathComplexity", "checkstyle:CyclomaticComplexity"})
public Iterator getSqlRecordIteratorBatch(
@Nonnull Comparable from,
boolean fromInclusive,
@Nonnull Comparable to,
boolean toInclusive,
boolean descending,
Data lastEntryKeyData
) {
boolean useCursor = lastEntryKeyData != null;
if (useCursor && !descending && !fromInclusive) {
throw new IllegalArgumentException("If `lastEntryKeyData` is not null then `from` must be inclusive");
}
if (useCursor && descending && !toInclusive) {
throw new IllegalArgumentException("If `lastEntryKeyData` is not null then `to` must be inclusive");
}
int order = SPECIAL_AWARE_COMPARATOR.compare(from, to);
if (order == 0) {
if (!fromInclusive || !toInclusive) {
return emptyIterator();
}
return getSqlRecordIteratorBatch(from, descending, lastEntryKeyData);
} else if (order > 0) {
return emptyIterator();
}
var subMap = descending
? recordMap.subMap(from, fromInclusive, to, toInclusive).descendingMap()
: recordMap.subMap(from, fromInclusive, to, toInclusive);
var indexKeyForLastEntryKeyData = descending ? to : from;
return subMap.entrySet()
.stream()
.map(
(Entry> es) -> {
var map = descending ? es.getValue().descendingMap() : es.getValue();
if (useCursor && SPECIAL_AWARE_COMPARATOR.compare(indexKeyForLastEntryKeyData, es.getKey()) == 0) {
map = map.tailMap(lastEntryKeyData, false);
}
return new IndexKeyEntries(es.getKey(), map.values().iterator());
}
)
.iterator();
}
@Override
public Set getRecords(Comparable value) {
takeReadLock();
try {
return toSingleResultSet(recordMap.get(value));
} finally {
releaseReadLock();
}
}
@Override
public Set getRecords(Set values) {
takeReadLock();
try {
MultiResultSet results = createMultiResultSet();
for (Comparable value : values) {
Map records;
records = recordMap.get(value);
if (records != null) {
copyToMultiResultSet(results, records);
}
}
return results;
} finally {
releaseReadLock();
}
}
@Override
public Set getRecords(Comparison comparison, Comparable searchedValue) {
switch (comparison) {
case LESS:
return getRecords(NULL, false, searchedValue, false);
case LESS_OR_EQUAL:
return getRecords(NULL, false, searchedValue, true);
case GREATER:
return getRecords(searchedValue, false, POSITIVE_INFINITY, true);
case GREATER_OR_EQUAL:
return getRecords(searchedValue, true, POSITIVE_INFINITY, true);
default:
throw new IllegalArgumentException("Unrecognized comparison: " + comparison);
}
}
@Override
public Set getRecords(Comparable from, boolean fromInclusive, Comparable to, boolean toInclusive) {
takeReadLock();
try {
int order = SPECIAL_AWARE_COMPARATOR.compare(from, to);
if (order == 0) {
if (!fromInclusive || !toInclusive) {
return emptySet();
}
return toSingleResultSet(recordMap.get(from));
} else if (order > 0) {
return emptySet();
}
MultiResultSet results = createMultiResultSet();
SortedMap> subMap =
recordMap.subMap(from, fromInclusive, to, toInclusive);
for (Map value : subMap.values()) {
copyToMultiResultSet(results, value);
}
return results;
} finally {
releaseReadLock();
}
}
/**
* Adds entry to the given index map without copying it.
* Needs to be invoked in a thread-safe way.
*
* @see IndexCopyBehavior
*/
private class AddFunctor implements IndexFunctor {
@Override
public Object invoke(Comparable value, QueryableEntry entry) {
return recordMap.computeIfAbsent(value, x -> new ConcurrentSkipListMap<>(DATA_COMPARATOR)).put(entry.getKeyData(),
entry);
}
}
/**
* Adds entry to the given index map copying it to secure exclusive access.
* Needs to be invoked in a thread-safe way.
*
* @see IndexCopyBehavior
*/
private class CopyOnWriteAddFunctor implements IndexFunctor {
@Override
public Object invoke(Comparable value, QueryableEntry entry) {
Object oldValue;
NavigableMap records = recordMap.get(value);
if (records == null) {
records = new TreeMap<>(DATA_COMPARATOR);
}
records = new TreeMap<>(records);
oldValue = records.put(entry.getKeyData(), entry);
recordMap.put(value, records);
return oldValue;
}
}
/**
* Removes entry from the given index map without copying it.
* Needs to be invoked in a thread-safe way.
*
* @see IndexCopyBehavior
*/
private class RemoveFunctor implements IndexFunctor {
@Override
public Object invoke(Comparable value, Data indexKey) {
Object oldValue;
Map records = recordMap.get(value);
if (records != null) {
oldValue = records.remove(indexKey);
if (records.isEmpty()) {
recordMap.remove(value);
}
} else {
oldValue = null;
}
return oldValue;
}
}
/**
* Removes entry from the given index map copying it to secure exclusive access.
* Needs to be invoked in a thread-safe way.
*
* @see IndexCopyBehavior
*/
private class CopyOnWriteRemoveFunctor implements IndexFunctor {
@Override
public Object invoke(Comparable value, Data indexKey) {
Object oldValue;
NavigableMap records = recordMap.get(value);
if (records != null) {
records = new TreeMap<>(records);
oldValue = records.remove(indexKey);
if (records.isEmpty()) {
recordMap.remove(value);
} else {
recordMap.put(value, records);
}
} else {
oldValue = null;
}
return oldValue;
}
}
private static final class IteratorFromBatch implements Iterator {
private final Iterator iterator;
private Iterator indexKeyIterator;
private IteratorFromBatch(@Nonnull Iterator iterator) {
this.iterator = iterator;
this.indexKeyIterator = iterator.hasNext() ? iterator.next().getEntries() : null;
}
@Override
public boolean hasNext() {
if (indexKeyIterator == null) {
return false;
}
if (indexKeyIterator.hasNext()) {
return true;
} else {
while (iterator.hasNext()) {
indexKeyIterator = iterator.next().getEntries();
if (indexKeyIterator.hasNext()) {
return true;
}
}
return false;
}
}
@Override
public QueryableEntry next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return indexKeyIterator.next();
}
}
private static class DataComparator implements Comparator {
@Override
public int compare(Data o1, Data o2) {
byte[] thisBytes = o1.toByteArray();
byte[] thatBytes = o2.toByteArray();
return Arrays.compare(thisBytes, thatBytes);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy