提交 ab48ac15 authored 作者: S.Vladykin's avatar S.Vladykin

new batched index lookup api

上级 21962651
...@@ -427,13 +427,8 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -427,13 +427,8 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
} }
@Override @Override
public int getPreferedLookupBatchSize() { public IndexLookupBatch createLookupBatch(TableFilter filter) {
// No batched lookups supported by default. // Lookup batching is not supported.
return 0; return null;
}
@Override
public List<Future<Cursor>> findBatched(TableFilter filter, List<SearchRow> firstLastPairs) {
throw DbException.throwInternalError("Must not be called if getPreferedLookupBatchSize() is 0.");
} }
} }
...@@ -259,27 +259,11 @@ public interface Index extends SchemaObject { ...@@ -259,27 +259,11 @@ public interface Index extends SchemaObject {
void setSortedInsertMode(boolean sortedInsertMode); void setSortedInsertMode(boolean sortedInsertMode);
/** /**
* If this index can do batched lookups, it may return it's preferred batch size, * Creates new lookup batch. Note that returned {@link IndexLookupBatch} instance
* otherwise it must return 0. * can be used multiple times.
* *
* @return preferred batch size or 0 if lookup batching is not supported * @param filter Table filter.
* @see #findBatched(TableFilter, Collection) * @return Created batch or {@code null} if batched lookup is not supported by this index.
*/ */
int getPreferedLookupBatchSize(); IndexLookupBatch createLookupBatch(TableFilter filter);
/**
* Do batched lookup over the given collection of {@link SearchRow} pairs as in
* {@link #find(TableFilter, SearchRow, SearchRow)}.
* <br/><br/>
* Correct implementation must always return number of future cursors equal to
* {@code firstLastPairs.size() / 2}. Instead of {@link Future} containing empty
* {@link Cursor} it is possible to put {@code null} in result list.
*
* @param filter the table filter
* @param firstLastPairs List of batched search row pairs as in
* {@link #find(TableFilter, SearchRow, SearchRow)}, the collection will be reused by H2,
* thus it makes sense to defensively copy contents if needed.
* @return batched cursors for respective search row pairs in the same order
*/
List<Future<Cursor>> findBatched(TableFilter filter, List<SearchRow> firstLastPairs);
} }
...@@ -389,12 +389,8 @@ public class MultiVersionIndex implements Index { ...@@ -389,12 +389,8 @@ public class MultiVersionIndex implements Index {
} }
@Override @Override
public int getPreferedLookupBatchSize() { public IndexLookupBatch createLookupBatch(TableFilter filter) {
return 0; // Lookup batching is not supported.
} return null;
@Override
public List<Future<Cursor>> findBatched(TableFilter filter, List<SearchRow> firstLastPairs) {
throw DbException.throwInternalError("Must never be called.");
} }
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
package org.h2.table; package org.h2.table;
import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -23,6 +24,7 @@ import org.h2.expression.Expression; ...@@ -23,6 +24,7 @@ import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn; import org.h2.expression.ExpressionColumn;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexLookupBatch;
import org.h2.index.IndexCondition; import org.h2.index.IndexCondition;
import org.h2.index.IndexCursor; import org.h2.index.IndexCursor;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -345,16 +347,22 @@ public class TableFilter implements ColumnResolver { ...@@ -345,16 +347,22 @@ public class TableFilter implements ColumnResolver {
if (join != null) { if (join != null) {
batch = join.startQuery(s); batch = join.startQuery(s);
} }
if (batch == null && index.getPreferedLookupBatchSize() != 0 && select != null && IndexLookupBatch lookupBatch = null;
select.getTopTableFilter() != this) { if (batch == null && select != null && select.getTopTableFilter() != this) {
batch = new JoinBatch(join); lookupBatch = index.createLookupBatch(this);
if (lookupBatch != null) {
batch = new JoinBatch(join);
}
} }
if (batch != null) { if (batch != null) {
if (nestedJoin != null) { if (nestedJoin != null) {
throw DbException.getUnsupportedException("nested join with batched index"); throw DbException.getUnsupportedException("nested join with batched index");
} }
if (lookupBatch == null) {
lookupBatch = index.createLookupBatch(this);
}
joinBatch = batch; joinBatch = batch;
joinFilter = batch.register(this); joinFilter = batch.register(this, lookupBatch);
} }
return batch; return batch;
} }
...@@ -1126,11 +1134,12 @@ public class TableFilter implements ColumnResolver { ...@@ -1126,11 +1134,12 @@ public class TableFilter implements ColumnResolver {
/** /**
* @param filter table filter * @param filter table filter
* @param lookupBatch lookup batch
*/ */
private JoinFilter register(TableFilter filter) { private JoinFilter register(TableFilter filter, IndexLookupBatch lookupBatch) {
assert filter != null; assert filter != null;
filtersCount++; filtersCount++;
return top = new JoinFilter(filter, top); return top = new JoinFilter(lookupBatch, filter, top);
} }
/** /**
...@@ -1365,23 +1374,15 @@ public class TableFilter implements ColumnResolver { ...@@ -1365,23 +1374,15 @@ public class TableFilter implements ColumnResolver {
*/ */
private static final class JoinFilter { private static final class JoinFilter {
final TableFilter filter; final TableFilter filter;
final int batchSize;
final JoinFilter join; final JoinFilter join;
int id; int id;
/** IndexLookupBatch lookupBatch;
* Search rows batch.
*/
final ArrayList<SearchRow> searchRows;
private JoinFilter(TableFilter filter, JoinFilter join) { private JoinFilter(IndexLookupBatch lookupBatch, TableFilter filter, JoinFilter join) {
this.filter = filter; this.filter = filter;
this.join = join; this.join = join;
batchSize = filter.getIndex().getPreferedLookupBatchSize(); this.lookupBatch = lookupBatch != null ? lookupBatch : new FakeLookupBatch(filter);
if (batchSize < 0) {
throw DbException.throwInternalError("Index with negative preferred batch size.");
}
searchRows = New.arrayList(batchSize == 0 ? 2 : Math.min(batchSize * 2, 32));
} }
public Row getNullRow() { public Row getNullRow() {
...@@ -1393,10 +1394,7 @@ public class TableFilter implements ColumnResolver { ...@@ -1393,10 +1394,7 @@ public class TableFilter implements ColumnResolver {
} }
private boolean isBatchFull() { private boolean isBatchFull() {
if (batchSize == 0) { return lookupBatch.isBatchFull();
return searchRows.size() >= 2;
}
return searchRows.size() >= batchSize * 2;
} }
private boolean isOk(boolean ignoreJoinCondition) { private boolean isOk(boolean ignoreJoinCondition) {
...@@ -1408,62 +1406,40 @@ public class TableFilter implements ColumnResolver { ...@@ -1408,62 +1406,40 @@ public class TableFilter implements ColumnResolver {
private boolean collectSearchRows() { private boolean collectSearchRows() {
assert !isBatchFull(); assert !isBatchFull();
filter.cursor.prepare(filter.session, filter.indexConditions); IndexCursor c = filter.cursor;
if (filter.cursor.isAlwaysFalse()) { c.prepare(filter.session, filter.indexConditions);
if (c.isAlwaysFalse()) {
return false; return false;
} }
searchRows.add(filter.cursor.getStart()); lookupBatch.addSearchRows(c.getStart(), c.getEnd());
searchRows.add(filter.cursor.getEnd());
return true; return true;
} }
private JoinRow find(JoinRow current) { private JoinRow find(JoinRow current) {
assert current != null; assert current != null;
assert (searchRows.size() & 1) == 0 : "searchRows & 1, " + searchRows.size();
// searchRows are allowed to be empty when we have some null-rows and forced find call // lookupBatch is allowed to be empty when we have some null-rows and forced find call
if (!searchRows.isEmpty()) { List<Future<Cursor>> result = lookupBatch.find();
assert searchRows.size() >= 2: "searchRows >= 2, " + searchRows.size();
// go backwards and assign futures
List<Future<Cursor>> result; for (int i = result.size(); i > 0;) {
assert current.isRow(id - 1);
if (batchSize == 0) { if (current.row(id) == EMPTY_CURSOR) {
assert searchRows.size() == 2 : "2"; // outer join support - skip row with existing empty cursor
Cursor c = filter.index.find(filter, searchRows.get(0), searchRows.get(1)); current = current.prev;
result = Collections.<Future<Cursor>>singletonList(new DoneFuture<Cursor>(c)); continue;
} else {
result = filter.index.findBatched(filter, searchRows);
if (result == null) {
throw DbException.throwInternalError("Index.findBatched returned null");
}
} }
assert current.row(id) == null;
if (result.size() != searchRows.size() >>> 1) { Future<Cursor> future = result.get(--i);
throw DbException.throwInternalError("wrong number of futures"); if (future == null) {
current.updateRow(id, EMPTY_CURSOR, JoinRow.S_NULL, JoinRow.S_CURSOR);
} else {
current.updateRow(id, future, JoinRow.S_NULL, JoinRow.S_FUTURE);
} }
searchRows.clear(); if (current.prev == null || i == 0) {
break;
// go backwards and assign futures
ListIterator<Future<Cursor>> iter = result.listIterator(result.size());
for (;;) {
assert current.isRow(id - 1);
if (current.row(id) == EMPTY_CURSOR) {
// outer join support - skip row with existing empty cursor
current = current.prev;
continue;
}
assert current.row(id) == null;
Future<Cursor> future = iter.previous();
if (future == null) {
current.updateRow(id, EMPTY_CURSOR, JoinRow.S_NULL, JoinRow.S_CURSOR);
} else {
current.updateRow(id, future, JoinRow.S_NULL, JoinRow.S_FUTURE);
}
if (current.prev == null || !iter.hasPrevious()) {
break;
}
current = current.prev;
} }
current = current.prev;
} }
// handle empty cursors (because of outer joins) at the beginning // handle empty cursors (because of outer joins) at the beginning
...@@ -1611,4 +1587,76 @@ public class TableFilter implements ColumnResolver { ...@@ -1611,4 +1587,76 @@ public class TableFilter implements ColumnResolver {
return "JoinRow->" + Arrays.toString(row); return "JoinRow->" + Arrays.toString(row);
} }
} }
/**
* Fake Lookup batch for indexes which do not support batching but have to participate
* in batched joins.
*/
private static class FakeLookupBatch implements IndexLookupBatch {
final TableFilter filter;
SearchRow first;
SearchRow last;
boolean full;
final List<Future<Cursor>> result = new SingletonList<Future<Cursor>>();
/**
* @param index Index.
*/
public FakeLookupBatch(TableFilter filter) {
this.filter = filter;
}
@Override
public void addSearchRows(SearchRow first, SearchRow last) {
assert !full;
this.first = first;
this.last = last;
full = true;
}
@Override
public boolean isBatchFull() {
return full;
}
@Override
public List<Future<Cursor>> find() {
if (!full) {
return Collections.emptyList();
}
Cursor c = filter.getIndex().find(filter, first, last);
result.set(0, new DoneFuture<Cursor>(c));
full = false;
first = last = null;
return result;
}
}
/**
* Simple singleton list.
*/
private static class SingletonList<E> extends AbstractList<E> {
private E element;
@Override
public E get(int index) {
assert index == 0;
return element;
}
@Override
public E set(int index, E element) {
assert index == 0;
this.element = element;
return null;
}
@Override
public int size() {
return 1;
}
}
} }
...@@ -33,6 +33,7 @@ import org.h2.expression.Expression; ...@@ -33,6 +33,7 @@ import org.h2.expression.Expression;
import org.h2.index.BaseIndex; import org.h2.index.BaseIndex;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexLookupBatch;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.index.SingleRowCursor; import org.h2.index.SingleRowCursor;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -1094,11 +1095,31 @@ public class TestTableEngines extends TestBase { ...@@ -1094,11 +1095,31 @@ public class TestTableEngines extends TestBase {
} }
@Override @Override
public int getPreferedLookupBatchSize() { public IndexLookupBatch createLookupBatch(final TableFilter filter) {
return preferedBatchSize; final int preferedSize = preferedBatchSize;
return preferedSize == 0 ? null : new IndexLookupBatch() {
List<SearchRow> searchRows = New.arrayList();
@Override public boolean isBatchFull() {
return searchRows.size() >= preferedSize * 2;
}
@Override
public List<Future<Cursor>> find() {
List<Future<Cursor>> res = findBatched(filter, searchRows);
searchRows.clear();
return res;
}
@Override
public void addSearchRows(SearchRow first, SearchRow last) {
assert !isBatchFull();
searchRows.add(first);
searchRows.add(last);
}
};
} }
@Override
public List<Future<Cursor>> findBatched(final TableFilter filter, List<SearchRow> firstLastPairs) { public List<Future<Cursor>> findBatched(final TableFilter filter, List<SearchRow> firstLastPairs) {
ArrayList<Future<Cursor>> result = New.arrayList(firstLastPairs.size()); ArrayList<Future<Cursor>> result = New.arrayList(firstLastPairs.size());
final Random rnd = new Random(); final Random rnd = new Random();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论