/*
* 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.twostep;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Cursor;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowFactory;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.h2.engine.Session;
import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.result.Row;
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;
import static java.util.Collections.emptyIterator;
import static org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase.bubbleUp;
/**
* Sorted index.
*/
public final class GridMergeIndexSorted extends GridMergeIndex {
/** */
private static final IndexType TYPE = IndexType.createNonUnique(false);
/** */
private final Comparator<RowStream> streamCmp = new Comparator<RowStream>() {
@Override public int compare(RowStream o1, RowStream o2) {
// Nulls at the beginning.
if (o1 == null)
return -1;
if (o2 == null)
return 1;
return compareRows(o1.get(), o2.get());
}
};
/** */
private Map<UUID,RowStream[]> streamsMap;
/** */
private final Lock lock = new ReentrantLock();
/** */
private final Condition notEmpty = lock.newCondition();
/** */
private GridResultPage failPage;
/** */
private MergeStreamIterator it;
/**
* @param ctx Kernal context.
* @param tbl Table.
* @param name Index name,
* @param cols Columns.
*/
public GridMergeIndexSorted(
GridKernalContext ctx,
GridMergeTable tbl,
String name,
IndexColumn[] cols
) {
super(ctx, tbl, name, TYPE, cols);
}
/** {@inheritDoc} */
@Override public void setSources(Collection<ClusterNode> nodes, int segmentsCnt) {
super.setSources(nodes, segmentsCnt);
streamsMap = U.newHashMap(nodes.size());
RowStream[] streams = new RowStream[nodes.size() * segmentsCnt];
int i = 0;
for (ClusterNode node : nodes) {
RowStream[] segments = new RowStream[segmentsCnt];
for (int s = 0; s < segmentsCnt; s++)
streams[i++] = segments[s] = new RowStream();
if (streamsMap.put(node.id(), segments) != null)
throw new IllegalStateException();
}
it = new MergeStreamIterator(streams);
}
/** {@inheritDoc} */
@Override public boolean fetchedAll() {
return it.fetchedAll();
}
/** {@inheritDoc} */
@Override protected void addPage0(GridResultPage page) {
if (page.isFail()) {
lock.lock();
try {
if (failPage == null) {
failPage = page;
notEmpty.signalAll();
}
}
finally {
lock.unlock();
}
}
else {
UUID src = page.source();
streamsMap.get(src)[page.segmentId()].addPage(page);
}
}
/** {@inheritDoc} */
@Override protected void checkBounds(Row lastEvictedRow, SearchRow first, SearchRow last) {
// If our last evicted fetched row was smaller than the given lower bound,
// then we are ok. This is important for merge join to work.
if (lastEvictedRow != null && first != null && compareRows(lastEvictedRow, first) < 0)
return;
super.checkBounds(lastEvictedRow, first, last);
}
/** {@inheritDoc} */
@Override protected Cursor findAllFetched(List<Row> fetched, SearchRow first, SearchRow last) {
Iterator<Row> iter;
if (fetched.isEmpty())
iter = emptyIterator();
else if (first == null && last == null)
iter = fetched.iterator();
else {
int low = first == null ? 0 : binarySearchRow(fetched, first, firstRowCmp, false);
if (low == fetched.size())
iter = emptyIterator();
else {
int high = last == null ? fetched.size() : binarySearchRow(fetched, last, lastRowCmp, false);
iter = fetched.subList(low, high).iterator();
}
}
return new GridH2Cursor(iter);
}
/** {@inheritDoc} */
@Override public double getCost(Session ses, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) {
return getCostRangeIndex(masks, getRowCountApproximation(), filters, filter, sortOrder, false, allColumnsSet);
}
/** {@inheritDoc} */
@Override protected Cursor findInStream(@Nullable SearchRow first, @Nullable SearchRow last) {
return new FetchingCursor(first, last, it);
}
/**
* Iterator merging multiple row streams.
*/
private final class MergeStreamIterator implements Iterator<Row> {
/** */
private boolean first = true;
/** */
private volatile int off;
/** */
private boolean hasNext;
/** */
private final RowStream[] streams;
/**
* @param streams Streams.
*/
MergeStreamIterator(RowStream[] streams) {
assert !F.isEmpty(streams);
this.streams = streams;
}
/**
* @return {@code true} If fetched all.
*/
private boolean fetchedAll() {
return off == streams.length;
}
/**
*
*/
private void goFirst() {
assert first;
first = false;
for (int i = 0; i < streams.length; i++) {
RowStream s = streams[i];
if (!s.next()) {
streams[i] = null;
off++; // Move left bound.
}
}
if (off < streams.length)
Arrays.sort(streams, streamCmp);
}
/**
*
*/
private void goNext() {
if (off == streams.length)
return; // All streams are done.
if (streams[off].next())
bubbleUp(streams, off, streamCmp);
else
streams[off++] = null; // Move left bound and nullify empty stream.
}
/** {@inheritDoc} */
@Override public boolean hasNext() {
if (hasNext)
return true;
if (first)
goFirst();
else
goNext();
return hasNext = off < streams.length;
}
/** {@inheritDoc} */
@Override public Row next() {
if (!hasNext())
throw new NoSuchElementException();
hasNext = false;
return streams[off].get();
}
/** {@inheritDoc} */
@Override public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Row stream.
*/
private final class RowStream implements Pollable<GridResultPage> {
/** */
Iterator<Value[]> iter = emptyIterator();
/** */
Row cur;
/** */
GridResultPage nextPage;
/**
* @param page Page.
*/
private void addPage(GridResultPage page) {
assert !page.isFail();
if (page.isLast() && page.rowsInPage() == 0)
page = createDummyLastPage(page); // Terminate.
lock.lock();
try {
// We can fetch the next page only when we have polled the previous one.
assert nextPage == null;
nextPage = page;
notEmpty.signalAll();
}
finally {
lock.unlock();
}
}
/** {@inheritDoc} */
@Override public GridResultPage poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
for (;;) {
if (failPage != null)
return failPage;
GridResultPage page = nextPage;
if (page != null) {
// isLast && !isDummyLast
nextPage = page.isLast() && page.response() != null
? createDummyLastPage(page) : null; // Terminate with empty iterator.
return page;
}
if ((nanos = notEmpty.awaitNanos(nanos)) <= 0)
return null;
}
}
finally {
lock.unlock();
}
}
/**
* @return {@code true} If we successfully switched to the next row.
*/
private boolean next() {
cur = null;
iter = pollNextIterator(this, iter);
if (!iter.hasNext())
return false;
cur = GridH2RowFactory.create(iter.next());
return true;
}
/**
* @return Current row.
*/
private Row get() {
assert cur != null;
return cur;
}
}
}