提交 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 { ...@@ -4729,8 +4729,14 @@ public class Parser {
.substring(parseIndex)); .substring(parseIndex));
read("AS"); read("AS");
try { try {
Query query = parseSelect(); Query query;
query.prepare(); session.setParsingView(true);
try {
query = parseSelect();
query.prepare();
} finally {
session.setParsingView(false);
}
command.setSelect(query); command.setSelect(query);
} catch (DbException e) { } catch (DbException e) {
if (force) { if (force) {
......
...@@ -31,7 +31,7 @@ public class AlterView extends DefineCommand { ...@@ -31,7 +31,7 @@ public class AlterView extends DefineCommand {
public int update() { public int update() {
session.commit(true); session.commit(true);
session.getUser().checkRight(view, Right.ALL); session.getUser().checkRight(view, Right.ALL);
DbException e = view.recompile(session, false); DbException e = view.recompile(session, false, true);
if (e != null) { if (e != null) {
throw e; throw e;
} }
......
...@@ -84,19 +84,26 @@ class Optimizer { ...@@ -84,19 +84,26 @@ class Optimizer {
} }
private void calculateBestPlan() { private void calculateBestPlan() {
start = System.currentTimeMillis();
cost = -1; cost = -1;
if (filters.length == 1 || !isJoinReorderingEnabled()) { if (filters.length == 1 || !isJoinReorderingEnabled()) {
testPlan(filters); testPlan(filters);
} else if (filters.length <= MAX_BRUTE_FORCE_FILTERS) {
calculateBruteForceAll();
} else { } else {
calculateBruteForceSome(); start = System.currentTimeMillis();
random = new Random(0); if (filters.length <= MAX_BRUTE_FORCE_FILTERS) {
calculateGenetic(); calculateBruteForceAll();
} else {
calculateBruteForceSome();
random = new Random(0);
calculateGenetic();
}
} }
} }
private void calculateFakePlan() {
cost = -1;
bestPlan = new Plan(filters, filters.length, condition);
}
private boolean canStop(int x) { private boolean canStop(int x) {
if ((x & 127) == 0) { if ((x & 127) == 0) {
long t = System.currentTimeMillis() - start; long t = System.currentTimeMillis() - start;
...@@ -234,15 +241,24 @@ class Optimizer { ...@@ -234,15 +241,24 @@ class Optimizer {
/** /**
* Calculate the best query plan to use. * 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) {
calculateBestPlan(); if (parse) {
bestPlan.removeUnusableIndexConditions(); calculateFakePlan();
} else {
calculateBestPlan();
bestPlan.removeUnusableIndexConditions();
}
TableFilter[] f2 = bestPlan.getFilters(); TableFilter[] f2 = bestPlan.getFilters();
topFilter = f2[0]; topFilter = f2[0];
for (int i = 0; i < f2.length - 1; i++) { for (int i = 0; i < f2.length - 1; i++) {
f2[i].addJoin(f2[i + 1], false, false, null); f2[i].addJoin(f2[i + 1], false, false, null);
} }
if (parse) {
return;
}
for (TableFilter f : f2) { for (TableFilter f : f2) {
PlanItem item = bestPlan.getItem(f); PlanItem item = bestPlan.getItem(f);
f.setPlanItem(item); f.setPlanItem(item);
......
...@@ -863,7 +863,7 @@ public class Select extends Query { ...@@ -863,7 +863,7 @@ public class Select extends Query {
isQuickAggregateQuery = isEverything(optimizable); isQuickAggregateQuery = isEverything(optimizable);
} }
} }
cost = preparePlan(); cost = preparePlan(session.isParsingView());
if (distinct && session.getDatabase().getSettings().optimizeDistinct && if (distinct && session.getDatabase().getSettings().optimizeDistinct &&
!isGroupQuery && filters.size() == 1 && !isGroupQuery && filters.size() == 1 &&
expressions.size() == 1 && condition == null) { expressions.size() == 1 && condition == null) {
...@@ -900,8 +900,8 @@ public class Select extends Query { ...@@ -900,8 +900,8 @@ public class Select extends Query {
} }
if (sort != null && !isQuickAggregateQuery && !isGroupQuery) { if (sort != null && !isQuickAggregateQuery && !isGroupQuery) {
Index index = getSortIndex(); 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) { if (current.getIndexType().isScan() || current == index) {
topTableFilter.setIndex(index); topTableFilter.setIndex(index);
if (!topTableFilter.hasInComparisons()) { if (!topTableFilter.hasInComparisons()) {
...@@ -934,7 +934,7 @@ public class Select extends Query { ...@@ -934,7 +934,7 @@ public class Select extends Query {
getGroupByExpressionCount() > 0) { getGroupByExpressionCount() > 0) {
Index index = getGroupSortedIndex(); Index index = getGroupSortedIndex();
Index current = topTableFilter.getIndex(); Index current = topTableFilter.getIndex();
if (index != null && (current.getIndexType().isScan() || if (index != null && current != null && (current.getIndexType().isScan() ||
current == index)) { current == index)) {
topTableFilter.setIndex(index); topTableFilter.setIndex(index);
isGroupSortedQuery = true; isGroupSortedQuery = true;
...@@ -967,7 +967,7 @@ public class Select extends Query { ...@@ -967,7 +967,7 @@ public class Select extends Query {
} }
} }
private double preparePlan() { private double preparePlan(boolean parse) {
TableFilter[] topArray = topFilters.toArray( TableFilter[] topArray = topFilters.toArray(
new TableFilter[topFilters.size()]); new TableFilter[topFilters.size()]);
for (TableFilter t : topArray) { for (TableFilter t : topArray) {
...@@ -975,13 +975,15 @@ public class Select extends Query { ...@@ -975,13 +975,15 @@ public class Select extends Query {
} }
Optimizer optimizer = new Optimizer(topArray, condition, session); Optimizer optimizer = new Optimizer(topArray, condition, session);
optimizer.optimize(); optimizer.optimize(parse);
topTableFilter = optimizer.getTopFilter(); topTableFilter = optimizer.getTopFilter();
double planCost = optimizer.getCost(); double planCost = optimizer.getCost();
setEvaluatableRecursive(topTableFilter); setEvaluatableRecursive(topTableFilter);
topTableFilter.prepare(); if (!parse) {
topTableFilter.prepare();
}
return planCost; return planCost;
} }
......
...@@ -811,7 +811,7 @@ public class Database implements DataHandler { ...@@ -811,7 +811,7 @@ public class Database implements DataHandler {
if (obj instanceof TableView) { if (obj instanceof TableView) {
TableView view = (TableView) obj; TableView view = (TableView) obj;
if (view.isInvalid()) { if (view.isInvalid()) {
view.recompile(session, true); view.recompile(session, true, false);
if (!view.isInvalid()) { if (!view.isInvalid()) {
recompileSuccessful = true; recompileSuccessful = true;
} }
...@@ -819,17 +819,7 @@ public class Database implements DataHandler { ...@@ -819,17 +819,7 @@ public class Database implements DataHandler {
} }
} }
} while (recompileSuccessful); } while (recompileSuccessful);
// when opening a database, views are initialized before indexes, TableView.clearIndexCaches(session.getDatabase());
// 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);
}
}
}
} }
private void initMetaTables() { private void initMetaTables() {
......
...@@ -10,8 +10,8 @@ import java.util.HashMap; ...@@ -10,8 +10,8 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map;
import java.util.Random; import java.util.Random;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Command; import org.h2.command.Command;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
...@@ -20,6 +20,7 @@ import org.h2.command.Prepared; ...@@ -20,6 +20,7 @@ import org.h2.command.Prepared;
import org.h2.command.dml.SetTypes; import org.h2.command.dml.SetTypes;
import org.h2.constraint.Constraint; import org.h2.constraint.Constraint;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.ViewIndex;
import org.h2.jdbc.JdbcConnection; import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
...@@ -33,6 +34,7 @@ import org.h2.schema.Schema; ...@@ -33,6 +34,7 @@ import org.h2.schema.Schema;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
import org.h2.store.InDoubtTransaction; import org.h2.store.InDoubtTransaction;
import org.h2.store.LobStorageFrontend; import org.h2.store.LobStorageFrontend;
import org.h2.table.SubQueryInfo;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.SmallLRUCache; import org.h2.util.SmallLRUCache;
...@@ -109,6 +111,10 @@ public class Session extends SessionWithState { ...@@ -109,6 +111,10 @@ public class Session extends SessionWithState {
private final int queryCacheSize; private final int queryCacheSize;
private SmallLRUCache<String, Command> queryCache; private SmallLRUCache<String, Command> queryCache;
private long modificationMetaID = -1; 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 * Temporary LOBs from result sets. Those are kept for some time. The
...@@ -142,6 +148,25 @@ public class Session extends SessionWithState { ...@@ -142,6 +148,25 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN; 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 @Override
public ArrayList<String> getClusterServers() { public ArrayList<String> getClusterServers() {
return new ArrayList<String>(); return new ArrayList<String>();
...@@ -457,7 +482,12 @@ public class Session extends SessionWithState { ...@@ -457,7 +482,12 @@ public class Session extends SessionWithState {
} }
} }
Parser parser = new Parser(this); Parser parser = new Parser(this);
command = parser.prepareCommand(sql); 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 (queryCache != null) {
if (command.isCacheable()) { if (command.isCacheable()) {
queryCache.put(sql, command); queryCache.put(sql, command);
...@@ -1296,6 +1326,22 @@ public class Session extends SessionWithState { ...@@ -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 * 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 * committed (if it needs to be closed). This is done to delete temporary
...@@ -1476,6 +1522,10 @@ public class Session extends SessionWithState { ...@@ -1476,6 +1522,10 @@ public class Session extends SessionWithState {
closeTemporaryResults(); closeTemporaryResults();
} }
public void clearViewIndexCache() {
viewIndexCache = null;
}
@Override @Override
public void addTemporaryLob(Value v) { public void addTemporaryLob(Value v) {
if (v.getType() != Value.CLOB && v.getType() != Value.BLOB) { if (v.getType() != Value.CLOB && v.getType() != Value.BLOB) {
...@@ -1532,5 +1582,4 @@ public class Session extends SessionWithState { ...@@ -1532,5 +1582,4 @@ public class Session extends SessionWithState {
} }
} }
} }
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
*/ */
package org.h2.index; package org.h2.index;
import java.util.List;
import java.util.concurrent.Future;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
......
...@@ -17,7 +17,7 @@ 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 * Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups. * sequential batched lookups.
* *
* @see Index#createLookupBatch(TableFilter) * @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin * @author Sergi Vladykin
*/ */
public interface IndexLookupBatch { public interface IndexLookupBatch {
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
package org.h2.index; package org.h2.index;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Prepared;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.command.dml.SelectUnion; import org.h2.command.dml.SelectUnion;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -21,6 +21,7 @@ import org.h2.result.SearchRow; ...@@ -21,6 +21,7 @@ import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.table.TableView; import org.h2.table.TableView;
import org.h2.util.IntArray; import org.h2.util.IntArray;
...@@ -46,6 +47,14 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -46,6 +47,14 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private Query query; private Query query;
private final Session createSession; 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, public ViewIndex(TableView view, String querySQL,
ArrayList<Parameter> originalParameters, boolean recursive) { ArrayList<Parameter> originalParameters, boolean recursive) {
initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false)); initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false));
...@@ -58,8 +67,19 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -58,8 +67,19 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
this.indexMasks = null; 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, 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)); initBaseIndex(view, 0, null, null, IndexType.createNonUnique(false));
this.view = view; this.view = view;
this.querySQL = index.querySQL; this.querySQL = index.querySQL;
...@@ -69,7 +89,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -69,7 +89,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
this.createSession = session; this.createSession = session;
columns = new Column[0]; columns = new Column[0];
if (!recursive) { if (!recursive) {
query = getQuery(session, masks); query = getQuery(session, masks, filters, filter, sortOrder);
} }
} }
...@@ -129,20 +149,13 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -129,20 +149,13 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
return cachedCost.cost; return cachedCost.cost;
} }
} }
Query q = (Query) session.prepare(querySQL, true); Query q = prepareSubQuery(querySQL, session, masks, filters, filter, sortOrder, true);
if (masks != null) { if (masks != null) {
IntArray paramIndex = new IntArray(); for (int idx = 0; idx < masks.length; idx++) {
for (int i = 0; i < masks.length; i++) { int mask = masks[idx];
int mask = masks[i];
if (mask == 0) { if (mask == 0) {
continue; 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(); int nextParamIndex = q.getParameters().size() + view.getParameterOffset();
if ((mask & IndexCondition.EQUALITY) != 0) { if ((mask & IndexCondition.EQUALITY) != 0) {
Parameter param = new Parameter(nextParamIndex); Parameter param = new Parameter(nextParamIndex);
...@@ -162,7 +175,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -162,7 +175,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
} }
} }
String sql = q.getPlanSQL(); String sql = q.getPlanSQL();
q = (Query) session.prepare(sql, true); q = prepareSubQuery(sql, session, masks, filters, filter, sortOrder, false);
} }
double cost = q.getCost(); double cost = q.getCost();
cachedCost = new CostElement(); cachedCost = new CostElement();
...@@ -182,6 +195,22 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -182,6 +195,22 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
return find(filter.getSession(), null, null, intersection); 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, private Cursor find(Session session, SearchRow first, SearchRow last,
SearchRow intersection) { SearchRow intersection) {
if (recursive) { if (recursive) {
...@@ -284,8 +313,9 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -284,8 +313,9 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
param.setValue(v); param.setValue(v);
} }
private Query getQuery(Session session, int[] masks) { private Query getQuery(Session session, int[] masks,
Query q = (Query) session.prepare(querySQL, true); TableFilter[] filters, int filter, SortOrder sortOrder) {
Query q = prepareSubQuery(querySQL, session, masks, filters, filter, sortOrder, true);
if (masks == null) { if (masks == null) {
return q; return q;
} }
...@@ -368,7 +398,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -368,7 +398,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
} }
String sql = q.getPlanSQL(); String sql = q.getPlanSQL();
q = (Query) session.prepare(sql, true); q = prepareSubQuery(sql, session, masks, filters, filter, sortOrder, false);
return q; 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 { ...@@ -236,6 +236,11 @@ public abstract class Table extends SchemaObjectBase {
*/ */
public abstract Index getScanIndex(Session session); 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. * Get any unique index for this table if one exists.
* *
......
...@@ -202,10 +202,14 @@ public class TableFilter implements ColumnResolver { ...@@ -202,10 +202,14 @@ public class TableFilter implements ColumnResolver {
*/ */
public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter) { public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter) {
PlanItem item; PlanItem item;
SortOrder sortOrder = null;
if (select != null) {
sortOrder = select.getSortOrder();
}
if (indexConditions.size() == 0) { if (indexConditions.size() == 0) {
item = new PlanItem(); item = new PlanItem();
item.setIndex(table.getScanIndex(s)); item.setIndex(table.getScanIndex(s, null, filters, filter, sortOrder));
item.cost = item.getIndex().getCost(s, null, filters, filter, null); item.cost = item.getIndex().getCost(s, null, filters, filter, sortOrder);
} else { } else {
int len = table.getColumns().length; int len = table.getColumns().length;
int[] masks = new int[len]; int[] masks = new int[len];
...@@ -221,10 +225,6 @@ public class TableFilter implements ColumnResolver { ...@@ -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 = table.getBestPlanItem(s, masks, filters, filter, sortOrder);
item.setMasks(masks); item.setMasks(masks);
// The more index conditions, the earlier the table. // The more index conditions, the earlier the table.
...@@ -241,8 +241,9 @@ public class TableFilter implements ColumnResolver { ...@@ -241,8 +241,9 @@ public class TableFilter implements ColumnResolver {
} }
if (join != null) { if (join != null) {
setEvaluatable(join); setEvaluatable(join);
filter += nestedJoin == null ? 1 : 2; do {
assert filters[filter] == join; filter++;
} while (filters[filter] != join);
item.setJoinPlan(join.getBestPlanItem(s, filters, filter)); item.setJoinPlan(join.getBestPlanItem(s, filters, filter));
// TODO optimizer: calculate cost of a join: should use separate // TODO optimizer: calculate cost of a join: should use separate
// expected row number and lookup cost // expected row number and lookup cost
......
...@@ -8,10 +8,12 @@ package org.h2.table; ...@@ -8,10 +8,12 @@ package org.h2.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.User; import org.h2.engine.User;
...@@ -29,10 +31,8 @@ import org.h2.result.Row; ...@@ -29,10 +31,8 @@ import org.h2.result.Row;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.SynchronizedVerifier;
import org.h2.value.Value; import org.h2.value.Value;
/** /**
...@@ -51,8 +51,6 @@ public class TableView extends Table { ...@@ -51,8 +51,6 @@ public class TableView extends Table {
private ViewIndex index; private ViewIndex index;
private boolean recursive; private boolean recursive;
private DbException createException; private DbException createException;
private final SmallLRUCache<CacheKey, ViewIndex> indexCache =
SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE);
private long lastModificationCheck; private long lastModificationCheck;
private long maxDataModificationId; private long maxDataModificationId;
private User owner; private User owner;
...@@ -83,10 +81,10 @@ public class TableView extends Table { ...@@ -83,10 +81,10 @@ public class TableView extends Table {
String[] oldColumnNames = this.columnNames; String[] oldColumnNames = this.columnNames;
boolean oldRecursive = this.recursive; boolean oldRecursive = this.recursive;
init(querySQL, null, columnNames, session, recursive); init(querySQL, null, columnNames, session, recursive);
DbException e = recompile(session, force); DbException e = recompile(session, force, true);
if (e != null) { if (e != null) {
init(oldQuerySQL, null, oldColumnNames, session, oldRecursive); init(oldQuerySQL, null, oldColumnNames, session, oldRecursive);
recompile(session, true); recompile(session, true, false);
throw e; throw e;
} }
} }
...@@ -97,13 +95,17 @@ public class TableView extends Table { ...@@ -97,13 +95,17 @@ public class TableView extends Table {
this.columnNames = columnNames; this.columnNames = columnNames;
this.recursive = recursive; this.recursive = recursive;
index = new ViewIndex(this, querySQL, params, recursive); index = new ViewIndex(this, querySQL, params, recursive);
SynchronizedVerifier.check(indexCache);
indexCache.clear();
initColumnsAndTables(session); initColumnsAndTables(session);
} }
private static Query compileViewQuery(Session session, String sql) { 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)) { if (!(p instanceof Query)) {
throw DbException.getSyntaxError(sql, 0); throw DbException.getSyntaxError(sql, 0);
} }
...@@ -115,10 +117,11 @@ public class TableView extends Table { ...@@ -115,10 +117,11 @@ public class TableView extends Table {
* *
* @param session the session * @param session the session
* @param force if exceptions should be ignored * @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 * @return the exception if re-compiling this or any dependent view failed
* (only when force is disabled) * (only when force is disabled)
*/ */
public synchronized DbException recompile(Session session, boolean force) { public synchronized DbException recompile(Session session, boolean force, boolean clearIndexCache) {
try { try {
compileViewQuery(session, querySQL); compileViewQuery(session, querySQL);
} catch (DbException e) { } catch (DbException e) {
...@@ -130,17 +133,18 @@ public class TableView extends Table { ...@@ -130,17 +133,18 @@ public class TableView extends Table {
if (views != null) { if (views != null) {
views = New.arrayList(views); views = New.arrayList(views);
} }
SynchronizedVerifier.check(indexCache);
indexCache.clear();
initColumnsAndTables(session); initColumnsAndTables(session);
if (views != null) { if (views != null) {
for (TableView v : views) { for (TableView v : views) {
DbException e = v.recompile(session, force); DbException e = v.recompile(session, force, false);
if (e != null && !force) { if (e != null && !force) {
return e; return e;
} }
} }
} }
if (clearIndexCache) {
clearIndexCaches(database);
}
return force ? null : createException; return force ? null : createException;
} }
...@@ -228,30 +232,14 @@ public class TableView extends Table { ...@@ -228,30 +232,14 @@ public class TableView extends Table {
TableFilter[] filters, int filter, SortOrder sortOrder) { TableFilter[] filters, int filter, SortOrder sortOrder) {
PlanItem item = new PlanItem(); PlanItem item = new PlanItem();
item.cost = index.getCost(session, masks, filters, filter, sortOrder); item.cost = index.getCost(session, masks, filters, filter, sortOrder);
final CacheKey cacheKey = new CacheKey(masks, session); final CacheKey cacheKey = new CacheKey(masks, this);
Map<Object, ViewIndex> indexCache = session.getViewIndexCache(topQuery != null);
synchronized (this) { ViewIndex i = indexCache.get(cacheKey);
SynchronizedVerifier.check(indexCache); if (i == null) {
ViewIndex i2 = indexCache.get(cacheKey); i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder);
if (i2 != null) { indexCache.put(cacheKey, i);
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);
} }
item.setIndex(i);
return item; return item;
} }
...@@ -272,6 +260,10 @@ public class TableView extends Table { ...@@ -272,6 +260,10 @@ public class TableView extends Table {
return true; return true;
} }
public Query getTopQuery() {
return topQuery;
}
@Override @Override
public String getDropSQL() { public String getDropSQL() {
return "DROP VIEW IF EXISTS " + getSQL() + " CASCADE"; return "DROP VIEW IF EXISTS " + getSQL() + " CASCADE";
...@@ -412,9 +404,16 @@ public class TableView extends Table { ...@@ -412,9 +404,16 @@ public class TableView extends Table {
database.removeMeta(session, getId()); database.removeMeta(session, getId());
querySQL = null; querySQL = null;
index = null; index = null;
clearIndexCaches(database);
invalidate(); invalidate();
} }
public static void clearIndexCaches(Database database) {
for (Session s : database.getSessions(true)) {
s.clearViewIndexCache();
}
}
@Override @Override
public String getSQL() { public String getSQL() {
if (isTemporary()) { if (isTemporary()) {
...@@ -429,12 +428,18 @@ public class TableView extends Table { ...@@ -429,12 +428,18 @@ public class TableView extends Table {
@Override @Override
public Index getScanIndex(Session session) { 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) { if (createException != null) {
String msg = createException.getMessage(); String msg = createException.getMessage();
throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, throw DbException.get(ErrorCode.VIEW_IS_INVALID_2,
createException, getSQL(), msg); createException, getSQL(), msg);
} }
PlanItem item = getBestPlanItem(session, null, null, -1, null); PlanItem item = getBestPlanItem(session, masks, filters, filter, sortOrder);
return item.getIndex(); return item.getIndex();
} }
...@@ -584,11 +589,11 @@ public class TableView extends Table { ...@@ -584,11 +589,11 @@ public class TableView extends Table {
private static final class CacheKey { private static final class CacheKey {
private final int[] masks; 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.masks = masks;
this.session = session; this.view = view;
} }
@Override @Override
...@@ -596,7 +601,7 @@ public class TableView extends Table { ...@@ -596,7 +601,7 @@ public class TableView extends Table {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + Arrays.hashCode(masks); result = prime * result + Arrays.hashCode(masks);
result = prime * result + session.hashCode(); result = prime * result + view.hashCode();
return result; return result;
} }
...@@ -612,7 +617,7 @@ public class TableView extends Table { ...@@ -612,7 +617,7 @@ public class TableView extends Table {
return false; return false;
} }
CacheKey other = (CacheKey) obj; CacheKey other = (CacheKey) obj;
if (session != other.session) { if (view != other.view) {
return false; return false;
} }
if (!Arrays.equals(masks, other.masks)) { if (!Arrays.equals(masks, other.masks)) {
......
...@@ -41,6 +41,7 @@ import org.h2.result.Row; ...@@ -41,6 +41,7 @@ import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.SubQueryInfo;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableBase; import org.h2.table.TableBase;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
...@@ -70,6 +71,7 @@ public class TestTableEngines extends TestBase { ...@@ -70,6 +71,7 @@ public class TestTableEngines extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
testSubQueryInfo();
testEarlyFilter(); testEarlyFilter();
testEngineParams(); testEngineParams();
testSimpleQuery(); testSimpleQuery();
...@@ -342,7 +344,45 @@ public class TestTableEngines extends TestBase { ...@@ -342,7 +344,45 @@ public class TestTableEngines extends TestBase {
deleteDb("tableEngine"); 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 { private void testBatchedJoin() throws SQLException {
deleteDb("tableEngine"); deleteDb("tableEngine");
Connection conn = getConnection("tableEngine;OPTIMIZE_REUSE_RESULTS=0"); Connection conn = getConnection("tableEngine;OPTIMIZE_REUSE_RESULTS=0");
...@@ -462,7 +502,13 @@ public class TestTableEngines extends TestBase { ...@@ -462,7 +502,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) { private static void setBatchSize(ArrayList<TreeSetTable> tables, int... batchSizes) {
for (int i = 0; i < batchSizes.length; i++) { for (int i = 0; i < batchSizes.length; i++) {
int batchSize = batchSizes[i]; int batchSize = batchSizes[i];
...@@ -1204,9 +1250,37 @@ public class TestTableEngines extends TestBase { ...@@ -1204,9 +1250,37 @@ public class TestTableEngines extends TestBase {
return new IteratorCursor(subSet.iterator()); 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 @Override
public double getCost(Session session, int[] masks, public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) { 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); return getCostRangeIndex(masks, set.size(), filters, filter, sortOrder);
} }
......
...@@ -10,8 +10,9 @@ import java.sql.PreparedStatement; ...@@ -10,8 +10,9 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Session;
import org.h2.jdbc.JdbcConnection;
import org.h2.test.TestBase; import org.h2.test.TestBase;
/** /**
...@@ -33,7 +34,7 @@ public class TestView extends TestBase { ...@@ -33,7 +34,7 @@ public class TestView extends TestBase {
@Override @Override
public void test() throws SQLException { public void test() throws SQLException {
deleteDb("view"); deleteDb("view");
testSubQueryViewIndexCache();
testInnerSelectWithRownum(); testInnerSelectWithRownum();
testInnerSelectWithRange(); testInnerSelectWithRange();
testEmptyColumn(); testEmptyColumn();
...@@ -51,6 +52,53 @@ public class TestView extends TestBase { ...@@ -51,6 +52,53 @@ public class TestView extends TestBase {
deleteDb("view"); 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 { private void testInnerSelectWithRownum() throws SQLException {
Connection conn = getConnection("view"); Connection conn = getConnection("view");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
......
...@@ -1805,8 +1805,8 @@ explain select * from (select dir_num, count(*) as cnt from multi_pages t, b_ho ...@@ -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 t.bh_id=bh.id and bh.site='Hello' group by dir_num) as x
where cnt < 1000 order by dir_num asc; where cnt < 1000 order by dir_num asc;
> PLAN > 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 > rows (ordered): 1
select dir_num, count(*) as cnt from multi_pages t, b_holding bh 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; ...@@ -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'; SELECT TABLE_NAME, SQL FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='VIEW';
> TABLE_NAME SQL > 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_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 /* PUBLIC.PRIMARY_KEY_9: ID < 2 */ WHERE ID < 2 > 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 > rows: 2
SELECT * FROM TEST_A_SUB WHERE NAME IS NOT NULL; SELECT * FROM TEST_A_SUB WHERE NAME IS NOT NULL;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论