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

Merge branch 'viewindex' into batchview2

......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Server mode: executing "shutdown" left a thread on the server.
</li>
<li>The condition "in(select...)" did not work correctly in some cases if the subquery had an "order by".
</li>
<li>Issue #184: The Platform-independent zip had Windows line endings in Linux scripts.
......
......@@ -177,10 +177,9 @@ When using one of the following features for production, please ensure your use
is well tested (if possible with automated test cases). The areas that are not well tested are:
</p>
<ul>
<li>Platforms other than Windows XP, Linux, Mac OS X, or JVMs other than Sun 1.6 or 1.7
<li>Platforms other than Windows, Linux, Mac OS X, or JVMs other than Oracle 1.6, 1.7, 1.8.
</li><li>The features <code>AUTO_SERVER</code> and <code>AUTO_RECONNECT</code>.
</li><li>Cluster mode, 2-phase commit, savepoints.
</li><li>24/7 operation.
</li><li>Fulltext search.
</li><li>Operations on LOBs over 2 GB.
</li><li>The optimizer may not always select the best plan.
......
......@@ -4729,8 +4729,14 @@ public class Parser {
.substring(parseIndex));
read("AS");
try {
Query query = parseSelect();
query.prepare();
Query query;
session.setParsingView(true);
try {
query = parseSelect();
query.prepare();
} finally {
session.setParsingView(false);
}
command.setSelect(query);
} catch (DbException e) {
if (force) {
......
......@@ -905,8 +905,8 @@ public class Select extends Query {
}
if (sort != null && !isQuickAggregateQuery && !isGroupQuery) {
Index index = getSortIndex();
if (index != null) {
Index current = topTableFilter.getIndex();
Index current = topTableFilter.getIndex();
if (index != null && current != null) {
if (current.getIndexType().isScan() || current == index) {
topTableFilter.setIndex(index);
if (!topTableFilter.hasInComparisons()) {
......@@ -939,7 +939,7 @@ public class Select extends Query {
getGroupByExpressionCount() > 0) {
Index index = getGroupSortedIndex();
Index current = topTableFilter.getIndex();
if (index != null && (current.getIndexType().isScan() ||
if (index != null && current != null && (current.getIndexType().isScan() ||
current == index)) {
topTableFilter.setIndex(index);
isGroupSortedQuery = true;
......
......@@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Random;
import org.h2.api.ErrorCode;
import org.h2.command.Command;
......@@ -19,6 +20,7 @@ import org.h2.command.Prepared;
import org.h2.command.dml.SetTypes;
import org.h2.constraint.Constraint;
import org.h2.index.Index;
import org.h2.index.ViewIndex;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.message.Trace;
......@@ -111,6 +113,8 @@ public class Session extends SessionWithState {
private long modificationMetaID = -1;
private SubQueryInfo subQueryInfo;
private int parsingView;
private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
private HashMap<Object, ViewIndex> subQueryIndexCache;
/**
* Temporary LOBs from result sets. Those are kept for some time. The
......@@ -1313,6 +1317,22 @@ public class Session extends SessionWithState {
}
}
public Map<Object, ViewIndex> getViewIndexCache(boolean subQuery) {
if (subQuery) {
// for sub-queries we don't need to use LRU because it should not grow too large
// for a single query and by the end of the statement we will drop the whole cache
if (subQueryIndexCache == null) {
subQueryIndexCache = New.hashMap();
}
return subQueryIndexCache;
}
SmallLRUCache<Object, ViewIndex> cache = viewIndexCache;
if (cache == null) {
viewIndexCache = cache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE);
}
return cache;
}
/**
* Remember the result set and close it as soon as the transaction is
* committed (if it needs to be closed). This is done to delete temporary
......@@ -1490,9 +1510,14 @@ public class Session extends SessionWithState {
*/
public void endStatement() {
startStatement = -1;
subQueryIndexCache = null;
closeTemporaryResults();
}
public void clearViewIndexCache() {
viewIndexCache = null;
}
@Override
public void addTemporaryLob(Value v) {
if (v.getType() != Value.CLOB && v.getType() != Value.BLOB) {
......@@ -1549,5 +1574,4 @@ public class Session extends SessionWithState {
}
}
}
......@@ -122,7 +122,7 @@ public class SysProperties {
//*/
/**
* System property <code>h2.check2</code> (default: true).<br />
* System property <code>h2.check2</code> (default: false).<br />
* Additional assertions in the database engine.
*/
//## CHECK ##
......
......@@ -248,6 +248,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private static Query prepareSubQuery(String sql, Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder, boolean preliminary) {
assert filters != null;
Prepared p;
SubQueryInfo upper = session.getSubQueryInfo();
SubQueryInfo info = new SubQueryInfo(upper,
......
......@@ -12,6 +12,9 @@ import java.nio.ByteBuffer;
*/
public class WriteBuffer {
/**
* The maximum size of the buffer in order to be re-used after a clear operation.
*/
private static final int MAX_REUSE_CAPACITY = 4 * 1024 * 1024;
/**
......@@ -19,9 +22,24 @@ public class WriteBuffer {
*/
private static final int MIN_GROW = 1024 * 1024;
private ByteBuffer reuse = ByteBuffer.allocate(MIN_GROW);
/**
* The buffer that is used after a clear operation.
*/
private ByteBuffer reuse;
/**
* The current buffer (may be replaced if it is too small).
*/
private ByteBuffer buff;
private ByteBuffer buff = reuse;
public WriteBuffer(int initialSize) {
reuse = ByteBuffer.allocate(initialSize);
buff = reuse;
}
public WriteBuffer() {
this(MIN_GROW);
}
/**
* Write a variable size integer.
......
......@@ -347,6 +347,7 @@ public class TcpServerThread implements Runnable {
int status;
if (session.isClosed()) {
status = SessionRemote.STATUS_CLOSED;
stop = true;
} else {
status = getState(old);
}
......
......@@ -236,6 +236,11 @@ public abstract class Table extends SchemaObjectBase {
*/
public abstract Index getScanIndex(Session session);
public Index getScanIndex(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return getScanIndex(session);
}
/**
* Get any unique index for this table if one exists.
*
......
......@@ -172,10 +172,14 @@ public class TableFilter implements ColumnResolver {
*/
public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter) {
PlanItem item;
SortOrder sortOrder = null;
if (select != null) {
sortOrder = select.getSortOrder();
}
if (indexConditions.size() == 0) {
item = new PlanItem();
item.setIndex(table.getScanIndex(s));
item.cost = item.getIndex().getCost(s, null, filters, filter, null);
item.setIndex(table.getScanIndex(s, null, filters, filter, sortOrder));
item.cost = item.getIndex().getCost(s, null, filters, filter, sortOrder);
} else {
int len = table.getColumns().length;
int[] masks = new int[len];
......@@ -191,10 +195,6 @@ public class TableFilter implements ColumnResolver {
}
}
}
SortOrder sortOrder = null;
if (select != null) {
sortOrder = select.getSortOrder();
}
item = table.getBestPlanItem(s, masks, filters, filter, sortOrder);
item.setMasks(masks);
// The more index conditions, the earlier the table.
......@@ -211,8 +211,9 @@ public class TableFilter implements ColumnResolver {
}
if (join != null) {
setEvaluatable(join);
filter += nestedJoin == null ? 1 : 2;
assert filters[filter] == join;
do {
filter++;
} while (filters[filter] != join);
item.setJoinPlan(join.getBestPlanItem(s, filters, filter));
// TODO optimizer: calculate cost of a join: should use separate
// expected row number and lookup cost
......
......@@ -8,6 +8,7 @@ package org.h2.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import org.h2.api.ErrorCode;
import org.h2.command.Prepared;
import org.h2.command.dml.Query;
......@@ -29,10 +30,8 @@ import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.SynchronizedVerifier;
import org.h2.value.Value;
/**
......@@ -51,8 +50,6 @@ public class TableView extends Table {
private ViewIndex index;
private boolean recursive;
private DbException createException;
private final SmallLRUCache<CacheKey, ViewIndex> indexCache =
SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE);
private long lastModificationCheck;
private long maxDataModificationId;
private User owner;
......@@ -97,8 +94,6 @@ public class TableView extends Table {
this.columnNames = columnNames;
this.recursive = recursive;
index = new ViewIndex(this, querySQL, params, recursive);
SynchronizedVerifier.check(indexCache);
indexCache.clear();
initColumnsAndTables(session);
}
......@@ -136,8 +131,6 @@ public class TableView extends Table {
if (views != null) {
views = New.arrayList(views);
}
SynchronizedVerifier.check(indexCache);
indexCache.clear();
initColumnsAndTables(session);
if (views != null) {
for (TableView v : views) {
......@@ -234,30 +227,14 @@ public class TableView extends Table {
TableFilter[] filters, int filter, SortOrder sortOrder) {
PlanItem item = new PlanItem();
item.cost = index.getCost(session, masks, filters, filter, sortOrder);
final CacheKey cacheKey = new CacheKey(masks, session);
synchronized (this) {
SynchronizedVerifier.check(indexCache);
ViewIndex i2 = indexCache.get(cacheKey);
if (i2 != null) {
item.setIndex(i2);
return item;
}
}
// We cannot hold the lock during the ViewIndex creation or we risk ABBA
// deadlocks if the view creation calls back into H2 via something like
// a FunctionTable.
ViewIndex i2 = new ViewIndex(this, index, session, masks, filters, filter, sortOrder);
synchronized (this) {
// have to check again in case another session has beat us to it
ViewIndex i3 = indexCache.get(cacheKey);
if (i3 != null) {
item.setIndex(i3);
return item;
}
indexCache.put(cacheKey, i2);
item.setIndex(i2);
final CacheKey cacheKey = new CacheKey(masks, this);
Map<Object, ViewIndex> indexCache = session.getViewIndexCache(topQuery != null);
ViewIndex i = indexCache.get(cacheKey);
if (i == null) {
i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder);
indexCache.put(cacheKey, i);
}
item.setIndex(i);
return item;
}
......@@ -278,6 +255,10 @@ public class TableView extends Table {
return true;
}
public Query getTopQuery() {
return topQuery;
}
@Override
public String getDropSQL() {
return "DROP VIEW IF EXISTS " + getSQL() + " CASCADE";
......@@ -418,6 +399,9 @@ public class TableView extends Table {
database.removeMeta(session, getId());
querySQL = null;
index = null;
for (Session s : database.getSessions(true)) {
s.clearViewIndexCache();
}
invalidate();
}
......@@ -435,12 +419,18 @@ public class TableView extends Table {
@Override
public Index getScanIndex(Session session) {
return getBestPlanItem(session, null, null, -1, null).getIndex();
}
@Override
public Index getScanIndex(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
if (createException != null) {
String msg = createException.getMessage();
throw DbException.get(ErrorCode.VIEW_IS_INVALID_2,
createException, getSQL(), msg);
}
PlanItem item = getBestPlanItem(session, null, null, -1, null);
PlanItem item = getBestPlanItem(session, masks, filters, filter, sortOrder);
return item.getIndex();
}
......@@ -590,11 +580,11 @@ public class TableView extends Table {
private static final class CacheKey {
private final int[] masks;
private final Session session;
private final TableView view;
public CacheKey(int[] masks, Session session) {
public CacheKey(int[] masks, TableView view) {
this.masks = masks;
this.session = session;
this.view = view;
}
@Override
......@@ -602,7 +592,7 @@ public class TableView extends Table {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(masks);
result = prime * result + session.hashCode();
result = prime * result + view.hashCode();
return result;
}
......@@ -618,7 +608,7 @@ public class TableView extends Table {
return false;
}
CacheKey other = (CacheKey) obj;
if (session != other.session) {
if (view != other.view) {
return false;
}
if (!Arrays.equals(masks, other.masks)) {
......
......@@ -332,21 +332,20 @@ public class ToChar {
private static String zeroesAfterDecimalSeparator(BigDecimal number) {
final String numberStr = number.toString();
final int idx = numberStr.indexOf('.');
if (idx >= 0 ) {
int i = idx + 1;
boolean allZeroes = true;
for (; i < numberStr.length(); i++) {
if (numberStr.charAt(i) != '0') {
allZeroes = false;
break;
}
}
final char[] zeroes = new char[allZeroes ? numberStr.length() - idx - 1: i - 1 - idx];
Arrays.fill(zeroes, '0');
return String.valueOf(zeroes);
} else {
if (idx < 0) {
return "";
}
int i = idx + 1;
boolean allZeroes = true;
for (; i < numberStr.length(); i++) {
if (numberStr.charAt(i) != '0') {
allZeroes = false;
break;
}
}
final char[] zeroes = new char[allZeroes ? numberStr.length() - idx - 1: i - 1 - idx];
Arrays.fill(zeroes, '0');
return String.valueOf(zeroes);
}
private static void addSign(StringBuilder output, int signum,
......
......@@ -41,6 +41,7 @@ import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.IndexColumn;
import org.h2.table.SubQueryInfo;
import org.h2.table.Table;
import org.h2.table.TableBase;
import org.h2.table.TableFilter;
......@@ -350,11 +351,20 @@ public class TestTableEngines extends TestBase {
Statement stat = conn.createStatement();
stat.execute("create table SUB_QUERY_TEST(id int primary key, name varchar) ENGINE \"" +
TreeSetIndexTableEngine.class.getName() + "\"");
stat.execute("create table t1(id int, birthday date)");
stat.executeQuery("select t1.birthday from t1, "
+ "(select t2.id from sub_query_test t2, "
+ "(select t3.id from sub_query_test t3 where t3.name = '') t4 "
+ "where t2.id = t4.id) t5 where t1.id = t5.id");
// test sub-queries
stat.executeQuery("select * from "
+ "(select t2.id from "
+ "(select t3.id from sub_query_test t3 where t3.name = '') t4, "
+ "sub_query_test t2 "
+ "where t2.id = t4.id) t5").next();
// test view 1
stat.execute("create view t4 as (select t3.id from sub_query_test t3 where t3.name = '')");
stat.executeQuery("select * from "
+ "(select t2.id from t4, sub_query_test t2 where t2.id = t4.id) t5").next();
// test view 2
stat.execute("create view t5 as "
+ "(select t2.id from t4, sub_query_test t2 where t2.id = t4.id)");
stat.executeQuery("select * from t5").next();
deleteDb("testSubQueryInfo");
}
......@@ -477,7 +487,13 @@ public class TestTableEngines extends TestBase {
}
}
}
private static void assert0(boolean condition, String message) {
if (!condition) {
throw new AssertionError(message);
}
}
private static void setBatchSize(ArrayList<TreeSetTable> tables, int... batchSizes) {
for (int i = 0; i < batchSizes.length; i++) {
int batchSize = batchSizes[i];
......@@ -1224,13 +1240,26 @@ public class TestTableEngines extends TestBase {
return new IteratorCursor(subSet.iterator());
}
private void checkInfo(SubQueryInfo info) {
if (info.getUpper() == null) {
// check 1st level info
assert0(info.getFilters().length == 1, "getFilters().length " + info.getFilters().length);
String alias = info.getFilters()[info.getFilter()].getTableAlias();
assert0("T5".equals(alias), "alias: " + alias);
} else {
// check 2nd level info
assert0(info.getFilters().length == 2, "getFilters().length " + info.getFilters().length);
String alias = info.getFilters()[info.getFilter()].getTableAlias();
assert0("T4".equals(alias), "alias: " + alias);
checkInfo(info.getUpper());
}
}
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
if (getTable().getName().equals("SUB_QUERY_TEST")) {
if (session.getSubQueryInfo() == null) {
throw new IllegalStateException("No qubquery info found.");
}
checkInfo(session.getSubQueryInfo());
}
return getCostRangeIndex(masks, set.size(), filters, filter, sortOrder);
}
......
......@@ -1805,8 +1805,8 @@ explain select * from (select dir_num, count(*) as cnt from multi_pages t, b_ho
where t.bh_id=bh.id and bh.site='Hello' group by dir_num) as x
where cnt < 1000 order by dir_num asc;
> PLAN
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT X.DIR_NUM, X.CNT FROM ( SELECT DIR_NUM, COUNT(*) AS CNT FROM PUBLIC.MULTI_PAGES T /* PUBLIC.MULTI_PAGES.tableScan */ INNER JOIN PUBLIC.B_HOLDING BH /* PUBLIC.PRIMARY_KEY_3: ID = T.BH_ID */ ON 1=1 WHERE (BH.SITE = 'Hello') AND (T.BH_ID = BH.ID) GROUP BY DIR_NUM ) X /* SELECT DIR_NUM, COUNT(*) AS CNT FROM PUBLIC.MULTI_PAGES T /++ PUBLIC.MULTI_PAGES.tableScan ++/ INNER JOIN PUBLIC.B_HOLDING BH /++ PUBLIC.PRIMARY_KEY_3: ID = T.BH_ID ++/ ON 1=1 WHERE (BH.SITE = 'Hello') AND (T.BH_ID = BH.ID) GROUP BY DIR_NUM HAVING COUNT(*) <= ?1: CNT < 1000 */ WHERE CNT < 1000 ORDER BY 1
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT X.DIR_NUM, X.CNT FROM ( SELECT DIR_NUM, COUNT(*) AS CNT FROM PUBLIC.MULTI_PAGES T INNER JOIN PUBLIC.B_HOLDING BH ON 1=1 WHERE (BH.SITE = 'Hello') AND (T.BH_ID = BH.ID) GROUP BY DIR_NUM ) X /* SELECT DIR_NUM, COUNT(*) AS CNT FROM PUBLIC.MULTI_PAGES T /++ PUBLIC.MULTI_PAGES.tableScan ++/ INNER JOIN PUBLIC.B_HOLDING BH /++ PUBLIC.PRIMARY_KEY_3: ID = T.BH_ID ++/ ON 1=1 WHERE (BH.SITE = 'Hello') AND (T.BH_ID = BH.ID) GROUP BY DIR_NUM HAVING COUNT(*) <= ?1: CNT < 1000 */ WHERE CNT < 1000 ORDER BY 1
> rows (ordered): 1
select dir_num, count(*) as cnt from multi_pages t, b_holding bh
......@@ -6892,9 +6892,9 @@ CREATE VIEW TEST_A_SUB AS SELECT * FROM TEST_A WHERE ID < 2;
SELECT TABLE_NAME, SQL FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='VIEW';
> TABLE_NAME SQL
> ---------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> TEST_ALL CREATE FORCE VIEW PUBLIC.TEST_ALL(AID, A_NAME, BID, B_NAME) AS SELECT A.ID AS AID, A.NAME AS A_NAME, B.ID AS BID, B.NAME AS B_NAME FROM PUBLIC.TEST_A A /* PUBLIC.TEST_A.tableScan */ INNER JOIN PUBLIC.TEST_B B /* PUBLIC.PRIMARY_KEY_93: ID = A.ID */ ON 1=1 WHERE A.ID = B.ID
> TEST_A_SUB CREATE FORCE VIEW PUBLIC.TEST_A_SUB(ID, NAME) AS SELECT TEST_A.ID, TEST_A.NAME FROM PUBLIC.TEST_A /* PUBLIC.PRIMARY_KEY_9: ID < 2 */ WHERE ID < 2
> ---------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> TEST_ALL CREATE FORCE VIEW PUBLIC.TEST_ALL(AID, A_NAME, BID, B_NAME) AS SELECT A.ID AS AID, A.NAME AS A_NAME, B.ID AS BID, B.NAME AS B_NAME FROM PUBLIC.TEST_A A INNER JOIN PUBLIC.TEST_B B ON 1=1 WHERE A.ID = B.ID
> TEST_A_SUB CREATE FORCE VIEW PUBLIC.TEST_A_SUB(ID, NAME) AS SELECT TEST_A.ID, TEST_A.NAME FROM PUBLIC.TEST_A WHERE ID < 2
> rows: 2
SELECT * FROM TEST_A_SUB WHERE NAME IS NOT NULL;
......
......@@ -203,8 +203,8 @@ public class BinaryArithmeticStream {
Node n = tree;
for (int i = bitCount; i >= 0; i--) {
boolean goRight = ((code >> i) & 1) == 1;
int prob = MAX_PROBABILITY *
n.right.frequency / n.frequency;
int prob = (int) ((long) MAX_PROBABILITY *
n.right.frequency / n.frequency);
out.writeBit(goRight, prob);
n = goRight ? n.right : n.left;
}
......@@ -219,8 +219,8 @@ public class BinaryArithmeticStream {
public int read(In in) throws IOException {
Node n = tree;
while (n.left != null) {
int prob = MAX_PROBABILITY *
n.right.frequency / n.frequency;
int prob = (int) ((long) MAX_PROBABILITY *
n.right.frequency / n.frequency);
boolean goRight = in.readBit(prob);
n = goRight ? n.right : n.left;
}
......
......@@ -245,6 +245,17 @@ public class BitStream {
return n.value;
}
/**
* Get the number of bits of the Huffman code for this value.
*
* @param value the value
* @return the number of bits
*/
public int getBitCount(int value) {
int code = codes[value];
return 30 - Integer.numberOfLeadingZeros(code);
}
}
/**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论