/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.processors.query.h2.opt;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.processors.query.h2.H2Cursor;
import org.apache.ignite.internal.util.GridCursorIteratorWrapper;
import org.apache.ignite.internal.util.IgniteTree;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.offheap.unsafe.GridOffHeapSnapTreeMap;
import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeGuard;
import org.apache.ignite.internal.util.snaptree.SnapTreeMap;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.spi.indexing.IndexingQueryFilter;
import org.h2.engine.Session;
import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.index.SingleRowCursor;
import org.h2.message.DbException;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.jetbrains.annotations.Nullable;
/**
* Base class for snapshotable segmented tree indexes.
*/
@SuppressWarnings("ComparatorNotSerializable")
public class GridH2TreeIndex extends GridH2IndexBase implements Comparator<GridSearchRowPointer> {
/** */
private final IgniteNavigableMapTree[] segments;
/** */
private final boolean snapshotEnabled;
/**
* Constructor with index initialization. Creates index with single segment.
*
* @param name Index name.
* @param tbl Table.
* @param pk If this index is primary key.
* @param colsList Index columns list.
*/
@SuppressWarnings("unchecked")
public GridH2TreeIndex(String name, GridH2Table tbl, boolean pk, List<IndexColumn> colsList) {
this(name, tbl, pk, colsList, 1);
}
/**
* Constructor with index initialization.
*
* @param name Index name.
* @param tbl Table.
* @param pk If this index is primary key.
* @param colsList Index columns list.
* @param segmentsCnt Number of segments.
*/
@SuppressWarnings("unchecked")
public GridH2TreeIndex(String name, GridH2Table tbl, boolean pk, List<IndexColumn> colsList, int segmentsCnt) {
assert segmentsCnt > 0 : segmentsCnt;
IndexColumn[] cols = colsList.toArray(new IndexColumn[colsList.size()]);
IndexColumn.mapColumns(cols, tbl);
initBaseIndex(tbl, 0, name, cols,
pk ? IndexType.createPrimaryKey(false, false) : IndexType.createNonUnique(false, false, false));
segments = new IgniteNavigableMapTree[segmentsCnt];
final GridH2RowDescriptor desc = tbl.rowDescriptor();
if (desc == null || desc.memory() == null) {
snapshotEnabled = desc == null || desc.snapshotableIndex();
if (snapshotEnabled) {
for (int i = 0; i < segmentsCnt; i++) {
segments[i] = new IgniteNavigableMapTree(new SnapTreeMap<GridSearchRowPointer, GridH2Row>(this) {
@Override protected void afterNodeUpdate_nl(Node<GridSearchRowPointer, GridH2Row> node, Object val) {
if (val != null)
node.key = (GridSearchRowPointer)val;
}
@Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
if (key instanceof ComparableRow)
return (Comparable<? super SearchRow>)key;
return super.comparable(key);
}
});
}
}
else {
for (int i = 0; i < segmentsCnt; i++) {
segments[i] = new IgniteNavigableMapTree(
new ConcurrentSkipListMap<GridSearchRowPointer, GridH2Row>(
new Comparator<GridSearchRowPointer>() {
@Override public int compare(GridSearchRowPointer o1, GridSearchRowPointer o2) {
if (o1 instanceof ComparableRow)
return ((ComparableRow)o1).compareTo(o2);
if (o2 instanceof ComparableRow)
return -((ComparableRow)o2).compareTo(o1);
return compareRows(o1, o2);
}
}
));
}
}
}
else {
assert desc.snapshotableIndex() : desc;
snapshotEnabled = true;
for (int i = 0; i < segmentsCnt; i++) {
segments[i] = new IgniteNavigableMapTree(new GridOffHeapSnapTreeMap<GridSearchRowPointer, GridH2Row>(desc, desc, desc.memory(), desc.guard(), this) {
@Override protected void afterNodeUpdate_nl(long node, GridH2Row val) {
final long oldKey = keyPtr(node);
if (val != null) {
key(node, val);
guard.finalizeLater(new Runnable() {
@Override public void run() {
desc.createPointer(oldKey).decrementRefCount();
}
});
}
}
@Override protected Comparable<? super GridSearchRowPointer> comparable(Object key) {
if (key instanceof ComparableRow)
return (Comparable<? super SearchRow>)key;
return super.comparable(key);
}
});
}
}
initDistributedJoinMessaging(tbl);
}
/** {@inheritDoc} */
@Override protected IgniteTree doTakeSnapshot() {
assert snapshotEnabled;
int seg = threadLocalSegment();
IgniteNavigableMapTree tree = segments[seg];
return tree.clone();
}
/** {@inheritDoc} */
@Override protected final IgniteTree treeForRead(int seg) {
if (!snapshotEnabled)
return segments[seg];
IgniteTree res = threadLocalSnapshot();
if (res == null)
return segments[seg];
return res;
}
/** {@inheritDoc} */
@Override public void destroy() {
assert threadLocalSnapshot() == null;
super.destroy();
}
/** {@inheritDoc} */
@Override public long getRowCount(@Nullable Session ses) {
IndexingQueryFilter f = threadLocalFilter();
int seg = threadLocalSegment();
// Fast path if we don't need to perform any filtering.
if (f == null || f.forSpace((getTable()).spaceName()) == null)
try {
return treeForRead(seg).size();
} catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
GridCursor<GridH2Row> cursor = doFind(null, false, null);
long size = 0;
try {
while (cursor.next())
size++;
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
return size;
}
/** {@inheritDoc} */
@Override public long getRowCountApproximation() {
return table.getRowCountApproximation();
}
/** {@inheritDoc} */
@Override public int compare(GridSearchRowPointer r1, GridSearchRowPointer r2) {
// Second row here must be data row if first is a search row.
return -compareRows(r2, r1);
}
/** {@inheritDoc} */
@Override public String toString() {
SB sb = new SB((indexType.isUnique() ? "Unique index '" : "Index '") + getName() + "' [");
boolean first = true;
for (IndexColumn col : getIndexColumns()) {
if (first)
first = false;
else
sb.a(", ");
sb.a(col.getSQL());
}
sb.a(" ]");
return sb.toString();
}
/** {@inheritDoc} */
@Override public double getCost(Session ses, int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder, HashSet<Column> cols) {
long rowCnt = getRowCountApproximation();
double baseCost = getCostRangeIndex(masks, rowCnt, filters, filter, sortOrder, false, cols);
int mul = getDistributedMultiplier(ses, filters, filter);
return mul * baseCost;
}
/** {@inheritDoc} */
@Override public boolean canFindNext() {
return false;
}
/** {@inheritDoc} */
@Override public Cursor find(Session ses, @Nullable SearchRow first, @Nullable SearchRow last) {
return new H2Cursor(doFind(first, true, last), null);
}
/** {@inheritDoc} */
@Override public Cursor findNext(Session ses, SearchRow higherThan, SearchRow last) {
return new H2Cursor(doFind(higherThan, false, last), null);
}
/**
* Finds row with key equal one in given search row.
* WARNING!! Method call must be protected by {@link GridUnsafeGuard#begin()}
* {@link GridUnsafeGuard#end()} block.
*
* @param row Search row.
* @return Row.
*/
@Override public GridH2Row findOne(GridH2Row row) {
int seg = segmentForRow(row);
return segments[seg].findOne(row);
}
/**
* Returns sub-tree bounded by given values.
*
* @param first Lower bound.
* @param includeFirst Whether lower bound should be inclusive.
* @param last Upper bound always inclusive.
* @return Iterator over rows in given range.
*/
@SuppressWarnings("unchecked")
private GridCursor<GridH2Row> doFind(@Nullable SearchRow first, boolean includeFirst, @Nullable SearchRow last) {
int seg = threadLocalSegment();
IgniteTree t = treeForRead(seg);
return doFind0(t, first, includeFirst, last, threadLocalFilter());
}
/** {@inheritDoc} */
@Override protected final GridCursor<GridH2Row> doFind0(
IgniteTree t,
@Nullable SearchRow first,
boolean includeFirst,
@Nullable SearchRow last,
IndexingQueryFilter filter
) {
includeFirst &= first != null;
GridCursor<GridH2Row> range = subTree(t, comparable(first, includeFirst ? -1 : 1),
comparable(last, 1));
if (range == null)
return EMPTY_CURSOR;
return filter(range, filter);
}
/**
* @param row Row.
* @param bias Bias.
* @return Comparable row.
*/
private GridSearchRowPointer comparable(SearchRow row, int bias) {
if (row == null)
return null;
if (bias == 0 && row instanceof GridH2Row)
return (GridSearchRowPointer)row;
return new ComparableRow(row, bias);
}
/**
* Takes sup-map from given one.
*
* @param tree Tree.
* @param first Lower bound.
* @param last Upper bound.
* @return Sub-map.
*/
@SuppressWarnings({"IfMayBeConditional", "TypeMayBeWeakened"})
private GridCursor<GridH2Row> subTree(IgniteTree tree,
@Nullable GridSearchRowPointer first, @Nullable GridSearchRowPointer last) {
if (first != null && last != null && compare(first, last) > 0)
return null;
try {
// We take exclusive bounds because it is possible that one search row will be equal to multiple key rows
// in tree and we must return them all.
return tree.find(first, last);
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/**
* Gets iterator over all rows in this index.
*
* @return Rows iterator.
*/
GridCursor<GridH2Row> rows() {
return doFind(null, false, null);
}
/** {@inheritDoc} */
@Override public boolean canGetFirstOrLast() {
return true;
}
/** {@inheritDoc} */
@Override public Cursor findFirstOrLast(Session ses, boolean first) {
try {
int seg = threadLocalSegment();
IgniteTree t = treeForRead(seg);
GridH2Row row = (GridH2Row)(first ? t.findFirst() : t.findLast());
return new SingleRowCursor(row);
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/** {@inheritDoc} */
@Override public GridH2Row put(GridH2Row row) {
int seg = segmentForRow(row);
return segments[seg].put(row);
}
/** {@inheritDoc} */
@Override public GridH2Row remove(SearchRow row) {
GridSearchRowPointer comparable = comparable(row, 0);
int seg = segmentForRow(row);
return segments[seg].remove(comparable);
}
/** {@inheritDoc} */
@Override protected int segmentsCount() {
return segments.length;
}
/**
* Comparable row with bias. Will be used for queries to have correct bounds (in case of multicolumn index
* and query on few first columns we will multiple equal entries in tree).
*/
private final class ComparableRow implements GridSearchRowPointer, Comparable<SearchRow> {
/** */
private final SearchRow row;
/** */
private final int bias;
/**
* @param row Row.
* @param bias Bias.
*/
private ComparableRow(SearchRow row, int bias) {
this.row = row;
this.bias = bias;
}
/** {@inheritDoc} */
@Override public int compareTo(SearchRow o) {
int res = compareRows(o, row);
if (res == 0)
return bias;
return -res;
}
/** {@inheritDoc} */
@Override public boolean equals(Object obj) {
throw new IllegalStateException("Should never be called.");
}
/** {@inheritDoc} */
@Override public int getColumnCount() {
return row.getColumnCount();
}
/** {@inheritDoc} */
@Override public Value getValue(int idx) {
return row.getValue(idx);
}
/** {@inheritDoc} */
@Override public void setValue(int idx, Value v) {
row.setValue(idx, v);
}
/** {@inheritDoc} */
@Override public void setKeyAndVersion(SearchRow old) {
row.setKeyAndVersion(old);
}
/** {@inheritDoc} */
@Override public int getVersion() {
return row.getVersion();
}
/** {@inheritDoc} */
@Override public void setKey(long key) {
row.setKey(key);
}
/** {@inheritDoc} */
@Override public long getKey() {
return row.getKey();
}
/** {@inheritDoc} */
@Override public int getMemory() {
return row.getMemory();
}
/** {@inheritDoc} */
@Override public long pointer() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void incrementRefCount() {
throw new IllegalStateException();
}
/** {@inheritDoc} */
@Override public void decrementRefCount() {
throw new IllegalStateException();
}
}
/**
* Adapter from {@link NavigableMap} to {@link IgniteTree}.
*/
private static final class IgniteNavigableMapTree implements IgniteTree<GridSearchRowPointer, GridH2Row>, Cloneable {
/** Tree. */
private final NavigableMap<GridSearchRowPointer, GridH2Row> tree;
/**
* @param tree Tree.
*/
private IgniteNavigableMapTree(NavigableMap<GridSearchRowPointer, GridH2Row> tree) {
this.tree = tree;
}
/** {@inheritDoc} */
@Override public void invoke(GridSearchRowPointer key, Object x, InvokeClosure<GridH2Row> c) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override public GridH2Row put(GridH2Row val) {
return tree.put(val, val);
}
/** {@inheritDoc} */
@Override public GridH2Row findOne(GridSearchRowPointer key) {
return tree.get(key);
}
/** {@inheritDoc} */
@Override public GridCursor<GridH2Row> find(GridSearchRowPointer lower, GridSearchRowPointer upper)
throws IgniteCheckedException {
Collection<GridH2Row> rows;
if (lower == null && upper == null)
rows = tree.values();
else if (lower != null && upper == null)
rows = tree.tailMap(lower).values();
else if (lower == null)
rows = tree.headMap(upper).values();
else
rows = tree.subMap(lower, false, upper, false).values();
return new GridCursorIteratorWrapper<>(rows.iterator());
}
/** {@inheritDoc} */
@Override public GridH2Row findFirst() throws IgniteCheckedException {
Map.Entry<GridSearchRowPointer, GridH2Row> first = tree.firstEntry();
return (first == null) ? null : first.getValue();
}
/** {@inheritDoc} */
@Override public GridH2Row findLast() throws IgniteCheckedException {
Map.Entry<GridSearchRowPointer, GridH2Row> last = tree.lastEntry();
return (last == null) ? null : last.getValue();
}
/** {@inheritDoc} */
@Override public GridH2Row remove(GridSearchRowPointer key) {
return tree.remove(key);
}
/** {@inheritDoc} */
@Override public long size() {
return tree.size();
}
/** {@inheritDoc} */
@Override public IgniteNavigableMapTree clone() {
IgniteNavigableMapTree cp;
try {
cp = (IgniteNavigableMapTree)super.clone();
}
catch (final CloneNotSupportedException e) {
throw DbException.convert(e);
}
return new IgniteNavigableMapTree(cp.tree);
}
}
}