提交 5143028e authored 作者: Thomas Mueller Graf's avatar Thomas Mueller Graf

Merge branch 'master' of https://github.com/h2database/h2database

......@@ -4729,8 +4729,14 @@ public class Parser {
.substring(parseIndex));
read("AS");
try {
Query query = parseSelect();
Query query;
session.setParsingView(true);
try {
query = parseSelect();
query.prepare();
} finally {
session.setParsingView(false);
}
command.setSelect(query);
} catch (DbException e) {
if (force) {
......
......@@ -31,7 +31,7 @@ public class AlterView extends DefineCommand {
public int update() {
session.commit(true);
session.getUser().checkRight(view, Right.ALL);
DbException e = view.recompile(session, false);
DbException e = view.recompile(session, false, true);
if (e != null) {
throw e;
}
......
......@@ -84,11 +84,12 @@ class Optimizer {
}
private void calculateBestPlan() {
start = System.currentTimeMillis();
cost = -1;
if (filters.length == 1 || !isJoinReorderingEnabled()) {
testPlan(filters);
} else if (filters.length <= MAX_BRUTE_FORCE_FILTERS) {
} else {
start = System.currentTimeMillis();
if (filters.length <= MAX_BRUTE_FORCE_FILTERS) {
calculateBruteForceAll();
} else {
calculateBruteForceSome();
......@@ -96,6 +97,12 @@ class Optimizer {
calculateGenetic();
}
}
}
private void calculateFakePlan() {
cost = -1;
bestPlan = new Plan(filters, filters.length, condition);
}
private boolean canStop(int x) {
if ((x & 127) == 0) {
......@@ -234,15 +241,24 @@ class Optimizer {
/**
* Calculate the best query plan to use.
*
* @param parse If we do not need to really get the best plan because it is view a parsing stage.
*/
void optimize() {
void optimize(boolean parse) {
if (parse) {
calculateFakePlan();
} else {
calculateBestPlan();
bestPlan.removeUnusableIndexConditions();
}
TableFilter[] f2 = bestPlan.getFilters();
topFilter = f2[0];
for (int i = 0; i < f2.length - 1; i++) {
f2[i].addJoin(f2[i + 1], false, false, null);
}
if (parse) {
return;
}
for (TableFilter f : f2) {
PlanItem item = bestPlan.getItem(f);
f.setPlanItem(item);
......
......@@ -863,7 +863,7 @@ public class Select extends Query {
isQuickAggregateQuery = isEverything(optimizable);
}
}
cost = preparePlan();
cost = preparePlan(session.isParsingView());
if (distinct && session.getDatabase().getSettings().optimizeDistinct &&
!isGroupQuery && filters.size() == 1 &&
expressions.size() == 1 && condition == null) {
......@@ -900,8 +900,8 @@ public class Select extends Query {
}
if (sort != null && !isQuickAggregateQuery && !isGroupQuery) {
Index index = getSortIndex();
if (index != null) {
Index current = topTableFilter.getIndex();
if (index != null && current != null) {
if (current.getIndexType().isScan() || current == index) {
topTableFilter.setIndex(index);
if (!topTableFilter.hasInComparisons()) {
......@@ -934,7 +934,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;
......@@ -967,7 +967,7 @@ public class Select extends Query {
}
}
private double preparePlan() {
private double preparePlan(boolean parse) {
TableFilter[] topArray = topFilters.toArray(
new TableFilter[topFilters.size()]);
for (TableFilter t : topArray) {
......@@ -975,13 +975,15 @@ public class Select extends Query {
}
Optimizer optimizer = new Optimizer(topArray, condition, session);
optimizer.optimize();
optimizer.optimize(parse);
topTableFilter = optimizer.getTopFilter();
double planCost = optimizer.getCost();
setEvaluatableRecursive(topTableFilter);
if (!parse) {
topTableFilter.prepare();
}
return planCost;
}
......
......@@ -811,7 +811,7 @@ public class Database implements DataHandler {
if (obj instanceof TableView) {
TableView view = (TableView) obj;
if (view.isInvalid()) {
view.recompile(session, true);
view.recompile(session, true, false);
if (!view.isInvalid()) {
recompileSuccessful = true;
}
......@@ -819,17 +819,7 @@ public class Database implements DataHandler {
}
}
} while (recompileSuccessful);
// when opening a database, views are initialized before indexes,
// so they may not have the optimal plan yet
// this is not a problem, it is just nice to see the newest plan
for (Table obj : getAllTablesAndViews(false)) {
if (obj instanceof TableView) {
TableView view = (TableView) obj;
if (!view.isInvalid()) {
view.recompile(systemSession, true);
}
}
}
TableView.clearIndexCaches(session.getDatabase());
}
private void initMetaTables() {
......
......@@ -10,8 +10,8 @@ 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;
import org.h2.command.CommandInterface;
......@@ -20,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;
......@@ -33,6 +34,7 @@ import org.h2.schema.Schema;
import org.h2.store.DataHandler;
import org.h2.store.InDoubtTransaction;
import org.h2.store.LobStorageFrontend;
import org.h2.table.SubQueryInfo;
import org.h2.table.Table;
import org.h2.util.New;
import org.h2.util.SmallLRUCache;
......@@ -109,6 +111,10 @@ public class Session extends SessionWithState {
private final int queryCacheSize;
private SmallLRUCache<String, Command> queryCache;
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
......@@ -142,6 +148,25 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN;
}
public void setSubQueryInfo(SubQueryInfo subQueryInfo) {
this.subQueryInfo = subQueryInfo;
}
public SubQueryInfo getSubQueryInfo() {
return subQueryInfo;
}
public void setParsingView(boolean parsingView) {
// It can be recursive, thus implemented as counter.
this.parsingView += parsingView ? 1 : -1;
assert this.parsingView >= 0;
}
public boolean isParsingView() {
assert parsingView >= 0;
return parsingView != 0;
}
@Override
public ArrayList<String> getClusterServers() {
return new ArrayList<String>();
......@@ -457,7 +482,12 @@ public class Session extends SessionWithState {
}
}
Parser parser = new Parser(this);
try {
command = parser.prepareCommand(sql);
} finally {
// we can't reuse sub-query indexes, so just drop the whole cache
subQueryIndexCache = null;
}
if (queryCache != null) {
if (command.isCacheable()) {
queryCache.put(sql, command);
......@@ -1296,6 +1326,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 the cache should not
// grow too large for a single query (we drop the whole cache in the end of prepareLocal)
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
......@@ -1476,6 +1522,10 @@ public class Session extends SessionWithState {
closeTemporaryResults();
}
public void clearViewIndexCache() {
viewIndexCache = null;
}
@Override
public void addTemporaryLob(Value v) {
if (v.getType() != Value.CLOB && v.getType() != Value.BLOB) {
......@@ -1532,5 +1582,4 @@ public class Session extends SessionWithState {
}
}
}
......@@ -5,8 +5,6 @@
*/
package org.h2.index;
import java.util.List;
import java.util.concurrent.Future;
import org.h2.engine.Session;
import org.h2.result.Row;
import org.h2.result.SearchRow;
......
......@@ -17,7 +17,7 @@ import org.h2.result.SearchRow;
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups.
*
* @see Index#createLookupBatch(TableFilter)
* @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin
*/
public interface IndexLookupBatch {
......
......@@ -6,8 +6,8 @@
package org.h2.index;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.Prepared;
import org.h2.command.dml.Query;
import org.h2.command.dml.SelectUnion;
import org.h2.engine.Constants;
......@@ -21,6 +21,7 @@ import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.IntArray;
......@@ -46,6 +47,14 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private Query query;
private final Session createSession;
/**
* Constructor for the original index in {@link TableView}.
*
* @param view the table view
* @param querySQL the query SQL
* @param originalParameters the original parameters
* @param recursive if the view is recursive
*/
public ViewIndex(TableView view, String querySQL,
ArrayList<Parameter> originalParameters, boolean recursive) {
initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false));
......@@ -58,8 +67,19 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
this.indexMasks = null;
}
/**
* Constructor for plan item generation. Over this index the query will be executed.
*
* @param view the table view
* @param index the view index
* @param session the session
* @param masks the masks
* @param filters table filters
* @param filter current filter
* @param sortOrder sort order
*/
public ViewIndex(TableView view, ViewIndex index, Session session,
int[] masks) {
int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder) {
initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false));
this.view = view;
this.querySQL = index.querySQL;
......@@ -69,7 +89,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
this.createSession = session;
columns = new Column[0];
if (!recursive) {
query = getQuery(session, masks);
query = getQuery(session, masks, filters, filter, sortOrder);
}
}
......@@ -129,20 +149,13 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
return cachedCost.cost;
}
}
Query q = (Query) session.prepare(querySQL, true);
Query q = prepareSubQuery(querySQL, session, masks, filters, filter, sortOrder, true);
if (masks != null) {
IntArray paramIndex = new IntArray();
for (int i = 0; i < masks.length; i++) {
int mask = masks[i];
for (int idx = 0; idx < masks.length; idx++) {
int mask = masks[idx];
if (mask == 0) {
continue;
}
paramIndex.add(i);
}
int len = paramIndex.size();
for (int i = 0; i < len; i++) {
int idx = paramIndex.get(i);
int mask = masks[idx];
int nextParamIndex = q.getParameters().size() + view.getParameterOffset();
if ((mask & IndexCondition.EQUALITY) != 0) {
Parameter param = new Parameter(nextParamIndex);
......@@ -162,7 +175,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
}
}
String sql = q.getPlanSQL();
q = (Query) session.prepare(sql, true);
q = prepareSubQuery(sql, session, masks, filters, filter, sortOrder, false);
}
double cost = q.getCost();
cachedCost = new CostElement();
......@@ -182,6 +195,22 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
return find(filter.getSession(), null, null, intersection);
}
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,
masks, filters, filter, sortOrder, preliminary);
session.setSubQueryInfo(info);
try {
p = session.prepare(sql, true);
} finally {
session.setSubQueryInfo(upper);
}
return (Query) p;
}
private Cursor find(Session session, SearchRow first, SearchRow last,
SearchRow intersection) {
if (recursive) {
......@@ -284,8 +313,9 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
param.setValue(v);
}
private Query getQuery(Session session, int[] masks) {
Query q = (Query) session.prepare(querySQL, true);
private Query getQuery(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
Query q = prepareSubQuery(querySQL, session, masks, filters, filter, sortOrder, true);
if (masks == null) {
return q;
}
......@@ -368,7 +398,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
}
String sql = q.getPlanSQL();
q = (Query) session.prepare(sql, true);
q = prepareSubQuery(sql, session, masks, filters, filter, sortOrder, false);
return q;
}
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.table;
import org.h2.result.SortOrder;
/**
* Information about current sub-query being prepared.
*
* @author Sergi Vladykin
*/
public class SubQueryInfo {
private int[] masks;
private TableFilter[] filters;
private int filter;
private SortOrder sortOrder;
private boolean preliminary;
private SubQueryInfo upper;
/**
* @param upper upper level sub-query if any
* @param masks index conditions masks
* @param filters table filters
* @param filter current filter
* @param sortOrder sort order
* @param preliminary if this is a preliminary query optimization
* without global conditions
*/
public SubQueryInfo(SubQueryInfo upper, int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder, boolean preliminary) {
this.upper = upper;
this.masks = masks;
this.filters = filters;
this.filter = filter;
this.sortOrder = sortOrder;
this.preliminary = preliminary;
}
public SubQueryInfo getUpper() {
return upper;
}
public int[] getMasks() {
return masks;
}
public TableFilter[] getFilters() {
return filters;
}
public int getFilter() {
return filter;
}
public SortOrder getSortOrder() {
return sortOrder;
}
public boolean isPreliminary() {
return preliminary;
}
}
......@@ -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.
*
......
......@@ -202,10 +202,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];
......@@ -221,10 +225,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.
......@@ -241,8 +241,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,10 +8,12 @@ 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;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.Session;
import org.h2.engine.User;
......@@ -29,10 +31,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 +51,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;
......@@ -83,10 +81,10 @@ public class TableView extends Table {
String[] oldColumnNames = this.columnNames;
boolean oldRecursive = this.recursive;
init(querySQL, null, columnNames, session, recursive);
DbException e = recompile(session, force);
DbException e = recompile(session, force, true);
if (e != null) {
init(oldQuerySQL, null, oldColumnNames, session, oldRecursive);
recompile(session, true);
recompile(session, true, false);
throw e;
}
}
......@@ -97,13 +95,17 @@ 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);
}
private static Query compileViewQuery(Session session, String sql) {
Prepared p = session.prepare(sql);
Prepared p;
session.setParsingView(true);
try {
p = session.prepare(sql);
} finally {
session.setParsingView(false);
}
if (!(p instanceof Query)) {
throw DbException.getSyntaxError(sql, 0);
}
......@@ -115,10 +117,11 @@ public class TableView extends Table {
*
* @param session the session
* @param force if exceptions should be ignored
* @param clearIndexCache if we need to clear view index cache
* @return the exception if re-compiling this or any dependent view failed
* (only when force is disabled)
*/
public synchronized DbException recompile(Session session, boolean force) {
public synchronized DbException recompile(Session session, boolean force, boolean clearIndexCache) {
try {
compileViewQuery(session, querySQL);
} catch (DbException e) {
......@@ -130,17 +133,18 @@ 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) {
DbException e = v.recompile(session, force);
DbException e = v.recompile(session, force, false);
if (e != null && !force) {
return e;
}
}
}
if (clearIndexCache) {
clearIndexCaches(database);
}
return force ? null : createException;
}
......@@ -228,30 +232,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);
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;
}
......@@ -272,6 +260,10 @@ public class TableView extends Table {
return true;
}
public Query getTopQuery() {
return topQuery;
}
@Override
public String getDropSQL() {
return "DROP VIEW IF EXISTS " + getSQL() + " CASCADE";
......@@ -412,9 +404,16 @@ public class TableView extends Table {
database.removeMeta(session, getId());
querySQL = null;
index = null;
clearIndexCaches(database);
invalidate();
}
public static void clearIndexCaches(Database database) {
for (Session s : database.getSessions(true)) {
s.clearViewIndexCache();
}
}
@Override
public String getSQL() {
if (isTemporary()) {
......@@ -429,12 +428,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();
}
......@@ -584,11 +589,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
......@@ -596,7 +601,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;
}
......@@ -612,7 +617,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)) {
......
......@@ -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;
......@@ -70,6 +71,7 @@ public class TestTableEngines extends TestBase {
@Override
public void test() throws Exception {
testSubQueryInfo();
testEarlyFilter();
testEngineParams();
testSimpleQuery();
......@@ -343,6 +345,44 @@ public class TestTableEngines extends TestBase {
deleteDb("tableEngine");
}
private void testSubQueryInfo() throws SQLException {
deleteDb("testSubQueryInfo");
Connection conn = getConnection("testSubQueryInfo");
Statement stat = conn.createStatement();
stat.execute("create table SUB_QUERY_TEST(id int primary key, name varchar) ENGINE \"" +
TreeSetIndexTableEngine.class.getName() + "\"");
// 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();
// test select expressions
stat.execute("create table EXPR_TEST(id int) ENGINE \"" +
TreeSetIndexTableEngine.class.getName() + "\"");
stat.executeQuery("select * from (select (select id from EXPR_TEST x limit 1) a "
+ "from dual where 1 = (select id from EXPR_TEST y limit 1)) z").next();
// test select expressions 2
stat.execute("create table EXPR_TEST2(id int) ENGINE \"" +
TreeSetIndexTableEngine.class.getName() + "\"");
stat.executeQuery("select * from (select (select 1 from "
+ "(select (select 2 from EXPR_TEST) from EXPR_TEST2) ZZ) from dual)").next();
// test select expression plan
stat.execute("create table test_plan(id int primary key, name varchar)");
stat.execute("create index MY_NAME_INDEX on test_plan(name)");
checkPlan(stat, "select * from (select (select id from test_plan where name = 'z') from dual)",
"MY_NAME_INDEX");
deleteDb("testSubQueryInfo");
}
private void testBatchedJoin() throws SQLException {
deleteDb("tableEngine");
Connection conn = getConnection("tableEngine;OPTIMIZE_REUSE_RESULTS=0");
......@@ -463,6 +503,12 @@ 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];
......@@ -1204,9 +1250,37 @@ public class TestTableEngines extends TestBase {
return new IteratorCursor(subSet.iterator());
}
private static String alias(SubQueryInfo info) {
return info.getFilters()[info.getFilter()].getTableAlias();
}
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 = alias(info);
assert0("T5".equals(alias), "alias: " + alias);
} else {
// check 2nd level info
assert0(info.getFilters().length == 2, "getFilters().length " + info.getFilters().length);
String alias = alias(info);
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")) {
checkInfo(session.getSubQueryInfo());
} else if (getTable().getName().equals("EXPR_TEST")) {
assert0(session.getSubQueryInfo() == null, "select expression");
} else if (getTable().getName().equals("EXPR_TEST2")) {
String alias = alias(session.getSubQueryInfo());
assert0(alias.equals("ZZ"), "select expression sub-query: " + alias);
assert0(session.getSubQueryInfo().getUpper() == null, "upper");
}
return getCostRangeIndex(masks, set.size(), filters, filter, sortOrder);
}
......
......@@ -10,8 +10,9 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.api.ErrorCode;
import org.h2.engine.Session;
import org.h2.jdbc.JdbcConnection;
import org.h2.test.TestBase;
/**
......@@ -33,7 +34,7 @@ public class TestView extends TestBase {
@Override
public void test() throws SQLException {
deleteDb("view");
testSubQueryViewIndexCache();
testInnerSelectWithRownum();
testInnerSelectWithRange();
testEmptyColumn();
......@@ -51,6 +52,53 @@ public class TestView extends TestBase {
deleteDb("view");
}
public void testSubQueryViewIndexCache() throws SQLException {
if (config.networked) {
return;
}
Connection conn = getConnection("view");
Statement stat = conn.createStatement();
stat.execute("drop table test if exists");
stat.execute("create table test(id int primary key, name varchar(25) unique, age int unique)");
// check that initial cache size is empty
Session s = (Session) ((JdbcConnection) conn).getSession();
s.clearViewIndexCache();
assertTrue(s.getViewIndexCache(true).isEmpty());
assertTrue(s.getViewIndexCache(false).isEmpty());
// create view command should not affect caches
stat.execute("create view v as select * from test");
assertTrue(s.getViewIndexCache(true).isEmpty());
assertTrue(s.getViewIndexCache(false).isEmpty());
// check view index cache
stat.executeQuery("select * from v where id > 0").next();
int size1 = s.getViewIndexCache(false).size();
assertTrue(size1 > 0);
assertTrue(s.getViewIndexCache(true).isEmpty());
stat.executeQuery("select * from v where name = 'xyz'").next();
int size2 = s.getViewIndexCache(false).size();
assertTrue(size2 > size1);
assertTrue(s.getViewIndexCache(true).isEmpty());
// check we did not add anything to view cache if we run a sub-query
stat.executeQuery("select * from (select * from test) where age = 17").next();
int size3 = s.getViewIndexCache(false).size();
assertEquals(size2, size3);
assertTrue(s.getViewIndexCache(true).isEmpty());
// check clear works
s.clearViewIndexCache();
assertTrue(s.getViewIndexCache(false).isEmpty());
assertTrue(s.getViewIndexCache(true).isEmpty());
// drop everything
stat.execute("drop view v");
stat.execute("drop table test");
conn.close();
}
private void testInnerSelectWithRownum() throws SQLException {
Connection conn = getConnection("view");
Statement stat = conn.createStatement();
......
......@@ -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;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论