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