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

Batched join now getting initialized in Select.prepare() + query plan prints batched join info

上级 83f9141d
......@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.CommandInterface;
......@@ -36,6 +35,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn;
import org.h2.table.JoinBatch;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
......@@ -942,9 +942,14 @@ public class Select extends Query {
}
expressionArray = new Expression[expressions.size()];
expressions.toArray(expressionArray);
topTableFilter.prepareBatch(0);
isPrepared = true;
}
public JoinBatch getJoinBatch() {
return getTopTableFilter().getJoinBatch();
}
@Override
public double getCost() {
return cost;
......
......@@ -15,7 +15,8 @@ import org.h2.result.SearchRow;
* method {@link #isBatchFull()}} will return {@code true} or there are no more
* search rows to add. Then method {@link #find()} will be called to execute batched lookup.
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups.
* sequential batched lookups, moreover it can be reused for multiple queries for
* the same prepared statement.
*
* @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin
......@@ -47,4 +48,9 @@ public interface IndexLookupBatch {
* @return List of future cursors for collected search rows.
*/
List<Future<Cursor>> find();
/**
* Reset this batch to clear state. This method will be called before each query execution.
*/
void reset();
}
......@@ -28,65 +28,93 @@ import org.h2.value.ValueLong;
* @author Sergi Vladykin
*/
public final class JoinBatch {
private static final Cursor EMPTY_CURSOR = new Cursor() {
@Override
public boolean previous() {
return false;
}
@Override
public boolean next() {
return false;
}
@Override
public SearchRow getSearchRow() {
return null;
}
@Override
public Row get() {
return null;
}
@Override
public String toString() {
return "EMPTY_CURSOR";
}
};
private int filtersCount;
private JoinFilter[] filters;
private JoinFilter top;
private boolean started;
private JoinRow current;
private boolean found;
/**
* This filter joined after this batched join and can be used normally.
*/
private final TableFilter additionalFilter;
/**
* @param filtersCount number of filters participating in this batched join
* @param additionalFilter table filter after this batched join.
*/
public JoinBatch(TableFilter additionalFilter) {
public JoinBatch(int filtersCount, TableFilter additionalFilter) {
if (filtersCount > 32) {
// This is because we store state in a 64 bit field, 2 bits per joined table.
throw DbException.getUnsupportedException("Too many tables in join (at most 32 supported).");
}
filters = new JoinFilter[filtersCount];
this.additionalFilter = additionalFilter;
}
/**
* @param joinFilterId joined table filter id
* @return {@code true} if index really supports batching in this query
*/
public boolean isBatchedIndex(int joinFilterId) {
return filters[joinFilterId].isBatched();
}
/**
* Reset state of this batch.
*/
public void reset() {
current = null;
started = false;
found = false;
for (JoinFilter jf : filters) {
jf.reset();
}
if (additionalFilter != null) {
additionalFilter.reset();
}
}
/**
* @param filter table filter
* @param lookupBatch lookup batch
*/
public void register(TableFilter filter, IndexLookupBatch lookupBatch) {
assert filter != null;
filtersCount++;
top = new JoinFilter(lookupBatch, filter, top);
filters[top.id] = top;
}
/**
* @param filterId table filter id
* @param column column
......@@ -108,21 +136,9 @@ public final class JoinBatch {
}
private void start() {
if (filtersCount > 32) {
// This is because we store state in a 64 bit field, 2 bits per joined table.
throw DbException.getUnsupportedException("Too many tables in join (at most 32 supported).");
}
// fill filters
filters = new JoinFilter[filtersCount];
JoinFilter jf = top;
for (int i = 0; i < filtersCount; i++) {
jf.id = jf.filter.joinFilterId = i;
filters[i] = jf;
jf = jf.join;
}
// TODO if filters[0].isBatched() then use batching instead of top.filter.getIndexCursor()
// initialize current row
current = new JoinRow(new Object[filtersCount]);
current = new JoinRow(new Object[filters.length]);
current.updateRow(top.id, top.filter.getIndexCursor(), JoinRow.S_NULL, JoinRow.S_CURSOR);
// initialize top cursor
......@@ -188,7 +204,7 @@ public final class JoinBatch {
}
current.prev = null;
final int lastJfId = filtersCount - 1;
final int lastJfId = filters.length - 1;
int jfId = lastJfId;
while (current.row(jfId) == null) {
......@@ -325,17 +341,28 @@ public final class JoinBatch {
private static final class JoinFilter {
private final TableFilter filter;
private final JoinFilter join;
private int id;
private final int id;
private final boolean fakeBatch;
private final IndexLookupBatch lookupBatch;
private JoinFilter(IndexLookupBatch lookupBatch, TableFilter filter, JoinFilter join) {
this.filter = filter;
this.id = filter.getJoinFilterId();
this.join = join;
this.lookupBatch = lookupBatch != null ? lookupBatch : new FakeLookupBatch(filter);
fakeBatch = lookupBatch == null;
this.lookupBatch = fakeBatch ? new FakeLookupBatch(filter) : lookupBatch;
}
public Row getNullRow() {
private boolean isBatched() {
return !fakeBatch;
}
private void reset() {
lookupBatch.reset();
}
private Row getNullRow() {
return filter.getTable().getNullRow();
}
......@@ -353,7 +380,7 @@ public final class JoinBatch {
return filterOk && (ignoreJoinCondition || joinOk);
}
private boolean collectSearchRows() {
assert !isBatchFull();
IndexCursor c = filter.getIndexCursor();
......@@ -364,7 +391,7 @@ public final class JoinBatch {
lookupBatch.addSearchRows(c.getStart(), c.getEnd());
return true;
}
private JoinRow find(JoinRow current) {
assert current != null;
......@@ -403,13 +430,13 @@ public final class JoinBatch {
// the last updated row
return current;
}
@Override
public String toString() {
return "JoinFilter->" + filter;
}
}
/**
* Linked row in batched join.
*/
......@@ -504,7 +531,7 @@ public final class JoinBatch {
}
row = null;
}
/**
* Copy this JoinRow behind itself in linked list of all in progress rows.
*
......@@ -514,24 +541,24 @@ public final class JoinBatch {
private JoinRow copyBehind(int jfId) {
assert isCursor(jfId);
assert jfId + 1 == row.length || row[jfId + 1] == null;
Object[] r = new Object[row.length];
if (jfId != 0) {
System.arraycopy(row, 0, r, 0, jfId);
}
JoinRow copy = new JoinRow(r);
copy.state = state;
if (prev != null) {
copy.prev = prev;
prev.next = copy;
}
prev = copy;
copy.next = this;
return copy;
}
@Override
public String toString() {
return "JoinRow->" + Arrays.toString(row);
......@@ -559,6 +586,13 @@ public final class JoinBatch {
this.filter = filter;
}
@Override
public void reset() {
full = false;
first = last = null;
result.set(0, null);
}
@Override
public void addSearchRows(SearchRow first, SearchRow last) {
assert !full;
......
......@@ -60,7 +60,7 @@ public class TableFilter implements ColumnResolver {
* Batched join support.
*/
private JoinBatch joinBatch;
int joinFilterId = -1;
private int joinFilterId = -1;
/**
* Indicates that this filter is used in the plan.
......@@ -308,52 +308,77 @@ public class TableFilter implements ColumnResolver {
* Start the query. This will reset the scan counts.
*
* @param s the session
* @return join batch if query runs over index which supports batched lookups, null otherwise
*/
public JoinBatch startQuery(Session s) {
joinBatch = null;
joinFilterId = -1;
public void startQuery(Session s) {
this.session = s;
scanCount = 0;
if (nestedJoin != null) {
nestedJoin.startQuery(s);
}
if (join != null) {
join.startQuery(s);
}
}
/**
* Reset to the current position.
*/
public void reset() {
if (joinBatch != null && joinFilterId == 0) {
// reset join batch only on top table filter
joinBatch.reset();
return;
}
if (nestedJoin != null) {
nestedJoin.reset();
}
if (join != null) {
join.reset();
}
state = BEFORE_FIRST;
foundOne = false;
}
/**
* Attempt to initialize batched join.
*
* @param id join filter id (index of this table filter in join list)
* @return join batch if query runs over index which supports batched lookups, {@code null} otherwise
*/
public JoinBatch prepareBatch(int id) {
JoinBatch batch = null;
if (join != null) {
batch = join.startQuery(s);
batch = join.prepareBatch(id + 1);
}
IndexLookupBatch lookupBatch = null;
if (batch == null && select != null && select.getTopTableFilter() != this) {
if (batch == null && select != null && id != 0) {
// TODO session.getSubQueryInfo() instead of id != 0 + use upper level sub-query info
lookupBatch = index.createLookupBatch(this);
if (lookupBatch != null) {
batch = new JoinBatch(join);
batch = new JoinBatch(id + 1, join);
}
}
if (batch != null) {
if (nestedJoin != null) {
throw DbException.getUnsupportedException("nested join with batched index");
}
if (lookupBatch == null) {
if (lookupBatch == null && id != 0) {
// TODO session.getSubQueryInfo() instead of id != 0 + use upper level sub-query info
lookupBatch = index.createLookupBatch(this);
}
joinBatch = batch;
joinFilterId = id;
batch.register(this, lookupBatch);
}
return batch;
}
/**
* Reset to the current position.
*/
public void reset() {
if (nestedJoin != null) {
nestedJoin.reset();
}
if (join != null) {
join.reset();
}
state = BEFORE_FIRST;
foundOne = false;
public int getJoinFilterId() {
return joinFilterId;
}
public JoinBatch getJoinBatch() {
return joinBatch;
}
/**
......@@ -363,7 +388,7 @@ public class TableFilter implements ColumnResolver {
*/
public boolean next() {
if (joinBatch != null) {
// will happen only on topTableFilter since jbatch.next does not call join.next()
// will happen only on topTableFilter since joinBatch.next() does not call join.next()
return joinBatch.next();
}
if (state == AFTER_LAST) {
......@@ -722,6 +747,14 @@ public class TableFilter implements ColumnResolver {
if (index != null) {
buff.append('\n');
StatementBuilder planBuff = new StatementBuilder();
if (joinBatch != null) {
if (joinBatch.isBatchedIndex(joinFilterId)) {
planBuff.append("batched:true ");
} else if (joinFilterId != 0) {
// top table filter does not need to fake batching, it works as usual in this case
planBuff.append("batched:fake ");
}
}
planBuff.append(index.getPlanSQL());
if (indexConditions.size() > 0) {
planBuff.append(": ");
......
......@@ -1118,6 +1118,11 @@ public class TestTableEngines extends TestBase {
searchRows.add(first);
searchRows.add(last);
}
@Override
public void reset() {
searchRows.clear();
}
};
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论