提交 986af2a3 authored 作者: Thomas Mueller Graf's avatar Thomas Mueller Graf

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

......@@ -7,7 +7,7 @@ SELECT [ TOP term ] [ DISTINCT | ALL ] selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ] [ ORDER BY order [,...] ]
[ LIMIT expression [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] ]
[ [ LIMIT expression ] [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] ]
[ FOR UPDATE ]
","
Selects data from a table or multiple tables.
......
......@@ -1588,6 +1588,8 @@ The following file systems are included:
To work around this limitation, combine it with the split file system: <code>split:nioMapped:test</code>.
</li><li><code>memFS:</code> in-memory file system (slower than mem; experimental; mainly used for testing the database engine itself).
</li><li><code>memLZF:</code> compressing in-memory file system (slower than memFS but uses less memory; experimental; mainly used for testing the database engine itself).
</li><li><code>nioMemFS:</code> stores data outside of the VM's heap - useful for large memory DBs without incurring GC costs.
</li><li><code>nioMemLZF:</code> stores compressed data outside of the VM's heap - useful for large memory DBs without incurring GC costs.
</li></ul>
<p>
As an example, to use the the <code>nio</code> file system, use the following database URL:
......
......@@ -21,6 +21,14 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #195: The new Maven uses a .cmd file instead of a .bat file
</li>
<li>Issue #212: EXPLAIN PLAN for UPDATE statement did not display LIMIT expression
</li>
<li>Support OFFSET without LIMIT in SELECT
</li>
<li>Improve error message for METHOD_NOT_FOUND_1/90087
</li>
<li>CLOB and BLOB objects of removed rows were sometimes kept in the database file.
</li>
<li>Server mode: executing "shutdown" left a thread on the server.
......
......@@ -113,7 +113,7 @@
90084=Cannot drop last column {0}
90085=Index {0} belongs to constraint {1}
90086=Class {0} not found
90087=Method {0} not found
90087=Method {0} with matching arguments not found
90088=Unknown mode {0}
90089=Collation cannot be changed because there is a data table: {0}
90090=Schema {0} cannot be dropped
......
......@@ -1358,7 +1358,7 @@ public class ErrorCode {
/**
* The error with code <code>90087</code> is thrown when
* the specified method was not found in the class.
* a method with matching number of arguments was not found in the class.
* Example:
* <pre>
* CREATE ALIAS TO_BINARY FOR "java.lang.Long.toBinaryString(long)";
......
......@@ -68,6 +68,11 @@ public abstract class Command implements CommandInterface {
@Override
public abstract boolean isQuery();
/**
* Prepare join batching.
*/
public abstract void prepareJoinBatch();
/**
* Get the list of parameters.
*
......
......@@ -7,6 +7,8 @@ package org.h2.command;
import java.util.ArrayList;
import org.h2.api.DatabaseEventListener;
import org.h2.command.dml.Explain;
import org.h2.command.dml.Query;
import org.h2.expression.Parameter;
import org.h2.expression.ParameterInterface;
import org.h2.result.ResultInterface;
......@@ -44,6 +46,23 @@ public class CommandContainer extends Command {
return prepared.isQuery();
}
@Override
public void prepareJoinBatch() {
if (session.isJoinBatchEnabled()) {
prepareJoinBatch(prepared);
}
}
private static void prepareJoinBatch(Prepared prepared) {
if (prepared.isQuery()) {
if (prepared.getType() == CommandInterface.SELECT) {
((Query) prepared).prepareJoinBatch();
} else if (prepared.getType() == CommandInterface.EXPLAIN) {
prepareJoinBatch(((Explain) prepared).getCommand());
}
}
}
private void recompileIfRequired() {
if (prepared.needRecompile()) {
// TODO test with 'always recompile'
......@@ -65,6 +84,7 @@ public class CommandContainer extends Command {
}
prepared.prepare();
prepared.setModificationMetaId(mod);
prepareJoinBatch();
}
}
......
......@@ -44,6 +44,11 @@ class CommandList extends Command {
return updateCount;
}
@Override
public void prepareJoinBatch() {
command.prepareJoinBatch();
}
@Override
public ResultInterface query(int maxrows) {
ResultInterface result = command.query(maxrows);
......
......@@ -13,6 +13,8 @@ import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
......@@ -177,6 +179,14 @@ public class Parser {
CURRENT_TIME = 23, ROWNUM = 24;
private static final int SPATIAL_INTERSECTS = 25;
private static final Comparator<TableFilter> TABLE_FILTER_COMPARATOR =
new Comparator<TableFilter>() {
@Override
public int compare(TableFilter o1, TableFilter o2) {
return o1 == o2 ? 0 : compareTableFilters(o1, o2);
}
};
private final Database database;
private final Session session;
/**
......@@ -208,6 +218,7 @@ public class Parser {
private boolean rightsChecked;
private boolean recompileAlways;
private ArrayList<Parameter> indexedParameterList;
private int orderInFrom;
public Parser(Session session) {
this.database = session.getDatabase();
......@@ -704,7 +715,7 @@ public class Parser {
Update command = new Update(session);
currentPrepared = command;
int start = lastParseIndex;
TableFilter filter = readSimpleTableFilter();
TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter);
read("SET");
if (readIf("(")) {
......@@ -760,7 +771,7 @@ public class Parser {
return command;
}
private TableFilter readSimpleTableFilter() {
private TableFilter readSimpleTableFilter(int orderInFrom) {
Table table = readTableOrView();
String alias = null;
if (readIf("AS")) {
......@@ -772,7 +783,7 @@ public class Parser {
}
}
return new TableFilter(session, table, alias, rightsChecked,
currentSelect);
currentSelect, orderInFrom);
}
private Delete parseDelete() {
......@@ -784,7 +795,7 @@ public class Parser {
currentPrepared = command;
int start = lastParseIndex;
readIf("FROM");
TableFilter filter = readSimpleTableFilter();
TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter);
if (readIf("WHERE")) {
Expression condition = readExpression();
......@@ -1186,7 +1197,7 @@ public class Parser {
return top;
}
} else if (readIf("VALUES")) {
table = parseValuesTable().getTable();
table = parseValuesTable(0).getTable();
} else {
String tableName = readIdentifierWithSchema(null);
Schema schema = getSchema();
......@@ -1236,7 +1247,7 @@ public class Parser {
}
alias = readFromAlias(alias);
return new TableFilter(session, table, alias, rightsChecked,
currentSelect);
currentSelect, orderInFrom++);
}
private String readFromAlias(String alias) {
......@@ -1610,7 +1621,7 @@ public class Parser {
private TableFilter getNested(TableFilter n) {
String joinTable = Constants.PREFIX_JOIN + parseIndex;
TableFilter top = new TableFilter(session, getDualTable(true),
joinTable, rightsChecked, currentSelect);
joinTable, rightsChecked, currentSelect, n.getOrderInFrom());
top.addJoin(n, false, true, null);
return top;
}
......@@ -1774,7 +1785,7 @@ public class Parser {
if (readIf("OFFSET")) {
command.setOffset(readExpression().optimize(session));
if (!readIf("ROW")) {
read("ROWS");
readIf("ROWS");
}
}
if (readIf("FETCH")) {
......@@ -1873,6 +1884,38 @@ public class Parser {
TableFilter filter = readTableFilter(false);
parseJoinTableFilter(filter, command);
} while (readIf(","));
// Parser can reorder joined table filters, need to explicitly sort them to
// get the order as it was in the original query.
if (session.isForceJoinOrder()) {
sortTableFilters(command.getTopFilters());
}
}
private static void sortTableFilters(ArrayList<TableFilter> filters) {
if (filters.size() < 2) {
return;
}
// Most probably we are already sorted correctly.
boolean sorted = true;
TableFilter prev = filters.get(0);
for (int i = 1; i < filters.size(); i++) {
TableFilter next = filters.get(i);
if (compareTableFilters(prev, next) > 0) {
sorted = false;
break;
}
prev = next;
}
// If not, then sort manually.
if (!sorted) {
Collections.sort(filters, TABLE_FILTER_COMPARATOR);
}
}
private static int compareTableFilters(TableFilter o1, TableFilter o2) {
assert o1.getOrderInFrom() != o2.getOrderInFrom();
return o1.getOrderInFrom() > o2.getOrderInFrom() ? 1 : -1;
}
private void parseJoinTableFilter(TableFilter top, final Select command) {
......@@ -1976,7 +2019,7 @@ public class Parser {
// SYSTEM_RANGE(1,1)
Table dual = getDualTable(false);
TableFilter filter = new TableFilter(session, dual, null,
rightsChecked, currentSelect);
rightsChecked, currentSelect, 0);
command.addTableFilter(filter, true);
} else {
parseSelectSimpleFromPart(command);
......@@ -4312,7 +4355,7 @@ public class Parser {
private Select parseValues() {
Select command = new Select(session);
currentSelect = command;
TableFilter filter = parseValuesTable();
TableFilter filter = parseValuesTable(0);
ArrayList<Expression> list = New.arrayList();
list.add(new Wildcard(null, null));
command.setExpressions(list);
......@@ -4321,7 +4364,7 @@ public class Parser {
return command;
}
private TableFilter parseValuesTable() {
private TableFilter parseValuesTable(int orderInFrom) {
Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
TableFunction tf = (TableFunction) Function.getFunction(database,
"TABLE");
......@@ -4397,7 +4440,7 @@ public class Parser {
tf.doneWithParameters();
Table table = new FunctionTable(mainSchema, session, tf, tf);
TableFilter filter = new TableFilter(session, table, null,
rightsChecked, currentSelect);
rightsChecked, currentSelect, orderInFrom);
return filter;
}
......
......@@ -175,7 +175,7 @@ public class AlterTableAddConstraint extends SchemaCommand {
int id = getObjectId();
String name = generateConstraintName(table);
ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table);
TableFilter filter = new TableFilter(session, table, null, false, null);
TableFilter filter = new TableFilter(session, table, null, false, null, 0);
checkExpression.mapColumns(filter, 0);
checkExpression = checkExpression.optimize(session);
check.setExpression(checkExpression);
......@@ -294,6 +294,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
private static Index getUniqueIndex(Table t, IndexColumn[] cols) {
if (t.getIndexes() == null) {
return null;
}
for (Index idx : t.getIndexes()) {
if (canUseUniqueIndex(idx, t, cols)) {
return idx;
......@@ -303,6 +306,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
private static Index getIndex(Table t, IndexColumn[] cols, boolean moreColumnOk) {
if (t.getIndexes() == null) {
return null;
}
for (Index idx : t.getIndexes()) {
if (canUseIndex(idx, t, cols, moreColumnOk)) {
return idx;
......
......@@ -40,6 +40,10 @@ public class Explain extends Prepared {
this.command = command;
}
public Prepared getCommand() {
return command;
}
@Override
public void prepare() {
command.prepare();
......
......@@ -24,11 +24,6 @@ public class NoOperation extends Prepared {
return 0;
}
@Override
public boolean isQuery() {
return false;
}
@Override
public boolean isTransactional() {
return true;
......
......@@ -53,16 +53,6 @@ class Optimizer {
this.session = session;
}
/**
* Whether join reordering is enabled (it can be disabled by hint).
*
* @return {@code true} if yes
*/
private static boolean isJoinReorderingEnabled() {
OptimizerHints hints = OptimizerHints.get();
return hints == null || hints.getJoinReorderEnabled();
}
/**
* How many filter to calculate using brute force. The remaining filters are
* selected using a greedy algorithm which has a runtime of (1 + 2 + ... +
......@@ -85,7 +75,7 @@ class Optimizer {
private void calculateBestPlan() {
cost = -1;
if (filters.length == 1 || !isJoinReorderingEnabled()) {
if (filters.length == 1 || session.isForceJoinOrder()) {
testPlan(filters);
} else {
start = System.currentTimeMillis();
......@@ -242,7 +232,7 @@ 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.
* @param parse If we do not need to really get the best plan because it is a view parsing stage.
*/
void optimize(boolean parse) {
if (parse) {
......
/*
* 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.command.dml;
/**
* Thread local hints for H2 query optimizer. All the ongoing queries in the
* current thread will run with respect to these hints, so if they are needed
* only for a single operation it is preferable to setup and drop them in
* try-finally block.
*
* Currently works only in embedded mode.
*
* @author Sergi Vladykin
*/
public class OptimizerHints {
private static final ThreadLocal<OptimizerHints> HINTS =
new ThreadLocal<OptimizerHints>();
private boolean joinReorderEnabled = true;
/**
* Set thread local hints or {@code null} to drop any existing hints.
*
* @param hints the hints
*/
public static void set(OptimizerHints hints) {
if (hints != null) {
HINTS.set(hints);
} else {
HINTS.remove();
}
}
/**
* Get the current thread local hints or {@code null} if none.
*
* @return the hints
*/
public static OptimizerHints get() {
return HINTS.get();
}
/**
* Set whether reordering of tables (or anything else in the {@code FROM}
* clause) is enabled. By default is {@code true}.
*
* @param joinReorderEnabled Flag value.
*/
public void setJoinReorderEnabled(boolean joinReorderEnabled) {
this.joinReorderEnabled = joinReorderEnabled;
}
public boolean getJoinReorderEnabled() {
return joinReorderEnabled;
}
}
......@@ -71,6 +71,18 @@ public abstract class Query extends Prepared {
super(session);
}
/**
* Check if this is a UNION query.
*
* @return {@code true} if this is a UNION query
*/
public abstract boolean isUnion();
/**
* Prepare join batching.
*/
public abstract void prepareJoinBatch();
/**
* Execute the query without checking the cache. If a target is specified,
* the results are written to it, and the method returns null. If no target
......
......@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.CommandInterface;
......@@ -36,6 +35,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn;
import org.h2.table.JoinBatch;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
......@@ -87,6 +87,11 @@ public class Select extends Query {
super(session);
}
@Override
public boolean isUnion() {
return false;
}
/**
* Add a table to the query.
*
......@@ -634,18 +639,25 @@ public class Select extends Query {
topTableFilter.lock(session, exclusive, exclusive);
ResultTarget to = result != null ? result : target;
if (limitRows != 0) {
if (isQuickAggregateQuery) {
queryQuick(columnCount, to);
} else if (isGroupQuery) {
if (isGroupSortedQuery) {
queryGroupSorted(columnCount, to);
try {
if (isQuickAggregateQuery) {
queryQuick(columnCount, to);
} else if (isGroupQuery) {
if (isGroupSortedQuery) {
queryGroupSorted(columnCount, to);
} else {
queryGroup(columnCount, result);
}
} else if (isDistinctQuery) {
queryDistinct(to, limitRows);
} else {
queryGroup(columnCount, result);
queryFlat(columnCount, to, limitRows);
}
} finally {
JoinBatch jb = getJoinBatch();
if (jb != null) {
jb.reset(false);
}
} else if (isDistinctQuery) {
queryDistinct(to, limitRows);
} else {
queryFlat(columnCount, to, limitRows);
}
}
if (offsetExpr != null) {
......@@ -945,6 +957,30 @@ public class Select extends Query {
isPrepared = true;
}
@Override
public void prepareJoinBatch() {
ArrayList<TableFilter> list = New.arrayList();
TableFilter f = getTopTableFilter();
do {
if (f.getNestedJoin() != null) {
// we do not support batching with nested joins
return;
}
list.add(f);
f = f.getJoin();
} while (f != null);
TableFilter[] fs = list.toArray(new TableFilter[list.size()]);
// prepare join batch
JoinBatch jb = null;
for (int i = fs.length - 1; i >= 0; i--) {
jb = fs[i].prepareJoinBatch(jb, fs, i);
}
}
public JoinBatch getJoinBatch() {
return getTopTableFilter().getJoinBatch();
}
@Override
public double getCost() {
return cost;
......
......@@ -7,7 +7,6 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.engine.Session;
......@@ -72,6 +71,17 @@ public class SelectUnion extends Query {
this.left = query;
}
@Override
public boolean isUnion() {
return true;
}
@Override
public void prepareJoinBatch() {
left.prepareJoinBatch();
right.prepareJoinBatch();
}
public void setUnionType(int type) {
this.unionType = type;
}
......
......@@ -497,6 +497,24 @@ public class Set extends Prepared {
database.setRowFactory(rowFactory);
break;
}
case SetTypes.BATCH_JOINS: {
int value = getIntValue();
if (value != 0 && value != 1) {
throw DbException.getInvalidValueException("BATCH_JOINS",
getIntValue());
}
session.setJoinBatchEnabled(value == 1);
break;
}
case SetTypes.FORCE_JOIN_ORDER: {
int value = getIntValue();
if (value != 0 && value != 1) {
throw DbException.getInvalidValueException("FORCE_JOIN_ORDER",
getIntValue());
}
session.setForceJoinOrder(value == 1);
break;
}
default:
DbException.throwInternalError("type="+type);
}
......
......@@ -228,6 +228,16 @@ public class SetTypes {
*/
public static final int ROW_FACTORY = 43;
/**
* The type of SET BATCH_JOINS statement.
*/
public static final int BATCH_JOINS = 44;
/**
* The type of SET FORCE_JOIN_ORDER statement.
*/
public static final int FORCE_JOIN_ORDER = 45;
private static final ArrayList<String> TYPES = New.arrayList();
private SetTypes() {
......@@ -280,6 +290,8 @@ public class SetTypes {
list.add(QUERY_STATISTICS, "QUERY_STATISTICS");
list.add(QUERY_STATISTICS_MAX_ENTRIES, "QUERY_STATISTICS_MAX_ENTRIES");
list.add(ROW_FACTORY, "ROW_FACTORY");
list.add(BATCH_JOINS, "BATCH_JOINS");
list.add(FORCE_JOIN_ORDER, "FORCE_JOIN_ORDER");
}
/**
......
......@@ -171,6 +171,10 @@ public class Update extends Prepared {
if (condition != null) {
buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL()));
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
}
return buff.toString();
}
......
......@@ -17,6 +17,7 @@ import org.h2.command.Command;
import org.h2.command.CommandInterface;
import org.h2.command.Parser;
import org.h2.command.Prepared;
import org.h2.command.dml.Query;
import org.h2.command.dml.SetTypes;
import org.h2.constraint.Constraint;
import org.h2.index.Index;
......@@ -30,12 +31,14 @@ import org.h2.mvstore.db.TransactionStore.Change;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.result.LocalResult;
import org.h2.result.Row;
import org.h2.result.SortOrder;
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.table.TableFilter;
import org.h2.util.New;
import org.h2.util.SmallLRUCache;
import org.h2.value.Value;
......@@ -113,8 +116,11 @@ public class Session extends SessionWithState {
private long modificationMetaID = -1;
private SubQueryInfo subQueryInfo;
private int parsingView;
private int preparingQueryExpression;
private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
private HashMap<Object, ViewIndex> subQueryIndexCache;
private boolean joinBatchEnabled;
private boolean forceJoinOrder;
/**
* Temporary LOBs from result sets. Those are kept for some time. The
......@@ -148,12 +154,33 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN;
}
public void setForceJoinOrder(boolean forceJoinOrder) {
this.forceJoinOrder = forceJoinOrder;
}
public boolean isForceJoinOrder() {
return forceJoinOrder;
}
public void setJoinBatchEnabled(boolean joinBatchEnabled) {
this.joinBatchEnabled = joinBatchEnabled;
}
public boolean isJoinBatchEnabled() {
return joinBatchEnabled;
}
public Row createRow(Value[] data, int memory) {
return database.createRow(data, memory);
}
public void setSubQueryInfo(SubQueryInfo subQueryInfo) {
this.subQueryInfo = subQueryInfo;
public void pushSubQueryInfo(int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder) {
subQueryInfo = new SubQueryInfo(subQueryInfo, masks, filters, filter, sortOrder);
}
public void popSubQueryInfo() {
subQueryInfo = subQueryInfo.getUpper();
}
public SubQueryInfo getSubQueryInfo() {
......@@ -171,6 +198,24 @@ public class Session extends SessionWithState {
return parsingView != 0;
}
public void optimizeQueryExpression(Query query) {
// we have to hide current subQueryInfo if we are going to optimize query expression
SubQueryInfo tmp = subQueryInfo;
subQueryInfo = null;
preparingQueryExpression++;
try {
query.prepare();
} finally {
subQueryInfo = tmp;
preparingQueryExpression--;
}
}
public boolean isPreparingQueryExpression() {
assert preparingQueryExpression >= 0;
return preparingQueryExpression != 0;
}
@Override
public ArrayList<String> getClusterServers() {
return new ArrayList<String>();
......@@ -492,6 +537,7 @@ public class Session extends SessionWithState {
// we can't reuse sub-query indexes, so just drop the whole cache
subQueryIndexCache = null;
}
command.prepareJoinBatch();
if (queryCache != null) {
if (command.isCacheable()) {
queryCache.put(sql, command);
......
......@@ -9,6 +9,7 @@ import org.h2.command.dml.Query;
import org.h2.engine.Session;
import org.h2.result.LocalResult;
import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.value.Value;
......@@ -36,7 +37,7 @@ public class ConditionExists extends Condition {
@Override
public Expression optimize(Session session) {
query.prepare();
session.optimizeQueryExpression(query);
return this;
}
......
......@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.value.Value;
......@@ -117,7 +118,7 @@ public class ConditionInSelect extends Condition {
public Expression optimize(Session session) {
left = left.optimize(session);
query.setRandomAccessResult(true);
query.prepare();
session.optimizeQueryExpression(query);
if (query.getColumnCount() != 1) {
throw DbException.get(ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN);
}
......
......@@ -6,13 +6,13 @@
package org.h2.expression;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.dml.Query;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.ResultInterface;
import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueArray;
......@@ -70,7 +70,7 @@ public class Subquery extends Expression {
@Override
public Expression optimize(Session session) {
query.prepare();
session.optimizeQueryExpression(query);
return this;
}
......
......@@ -5,11 +5,15 @@
*/
package org.h2.index;
import java.util.Arrays;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.DbObject;
import org.h2.engine.Mode;
import org.h2.engine.Session;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.result.Row;
......@@ -21,6 +25,7 @@ import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.Value;
......@@ -148,65 +153,66 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* b-tree range index. This is the estimated cost required to search one
* row, and then iterate over the given number of rows.
*
* @param masks the search mask
* @param masks the IndexCondition search masks, one for each Column in the table
* @param rowCount the number of rows in the index
* @param filters all joined table filters
* @param filter the current table filter index
* @param sortOrder the sort order
* @return the estimated cost
*/
protected long getCostRangeIndex(int[] masks, long rowCount,
TableFilter[] filters, int filter, SortOrder sortOrder) {
protected final long getCostRangeIndex(int[] masks, long rowCount, TableFilter[] filters, int filter,
SortOrder sortOrder, boolean isScanIndex) {
rowCount += Constants.COST_ROW_OFFSET;
long cost = rowCount;
long rows = rowCount;
int totalSelectivity = 0;
if (masks == null) {
return cost;
}
for (int i = 0, len = columns.length; i < len; i++) {
Column column = columns[i];
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
if (i == columns.length - 1 && getIndexType().isUnique()) {
cost = 3;
long rowsCost = rowCount;
if (masks != null) {
for (int i = 0, len = columns.length; i < len; i++) {
Column column = columns[i];
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
if (i == columns.length - 1 && getIndexType().isUnique()) {
rowsCost = 3;
break;
}
totalSelectivity = 100 - ((100 - totalSelectivity) * (100 - column.getSelectivity()) / 100);
long distinctRows = rowCount * totalSelectivity / 100;
if (distinctRows <= 0) {
distinctRows = 1;
}
rowsCost = 2 + Math.max(rowCount / distinctRows, 1);
} else if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) {
rowsCost = 2 + rowCount / 4;
break;
} else if ((mask & IndexCondition.START) == IndexCondition.START) {
rowsCost = 2 + rowCount / 3;
break;
} else if ((mask & IndexCondition.END) == IndexCondition.END) {
rowsCost = rowCount / 3;
break;
} else {
break;
}
totalSelectivity = 100 - ((100 - totalSelectivity) *
(100 - column.getSelectivity()) / 100);
long distinctRows = rowCount * totalSelectivity / 100;
if (distinctRows <= 0) {
distinctRows = 1;
}
rows = Math.max(rowCount / distinctRows, 1);
cost = 2 + rows;
} else if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) {
cost = 2 + rows / 4;
break;
} else if ((mask & IndexCondition.START) == IndexCondition.START) {
cost = 2 + rows / 3;
break;
} else if ((mask & IndexCondition.END) == IndexCondition.END) {
cost = rows / 3;
break;
} else {
break;
}
}
// if the ORDER BY clause matches the ordering of this index,
// it will be cheaper than another index, so adjust the cost accordingly
// If the ORDER BY clause matches the ordering of this index,
// it will be cheaper than another index, so adjust the cost
// accordingly.
long sortingCost = 0;
if (sortOrder != null) {
sortingCost = 100 + rowCount / 10;
}
if (sortOrder != null && !isScanIndex) {
boolean sortOrderMatches = true;
int coveringCount = 0;
int[] sortTypes = sortOrder.getSortTypes();
TableFilter tableFilter = filters == null ? null : filters[filter];
for (int i = 0, len = sortTypes.length; i < len; i++) {
if (i >= indexColumns.length) {
// we can still use this index if we are sorting by more
// We can still use this index if we are sorting by more
// than it's columns, it's just that the coveringCount
// is lower than with an index that contains
// more of the order by columns
// more of the order by columns.
break;
}
Column col = sortOrder.getColumn(i, tableFilter);
......@@ -215,7 +221,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
break;
}
IndexColumn indexCol = indexColumns[i];
if (col != indexCol.column) {
if (!col.equals(indexCol.column)) {
sortOrderMatches = false;
break;
}
......@@ -229,13 +235,51 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
if (sortOrderMatches) {
// "coveringCount" makes sure that when we have two
// or more covering indexes, we choose the one
// that covers more
cost -= coveringCount;
// that covers more.
sortingCost = 100 - coveringCount;
}
}
return cost;
// If we have two indexes with the same cost, and one of the indexes can
// satisfy the query without needing to read from the primary table,
// make that one slightly lower cost.
boolean needsToReadFromScanIndex = true;
if (!isScanIndex) {
HashSet<Column> set1 = New.hashSet();
for (int i = 0; i < filters.length; i++) {
if (filters[i].getSelect() != null) {
filters[i].getSelect().isEverything(ExpressionVisitor.getColumnsVisitor(set1));
}
}
if (!set1.isEmpty()) {
HashSet<Column> set2 = New.hashSet();
for (Column c : set1) {
if (c.getTable() == getTable()) {
set2.add(c);
}
}
set2.removeAll(Arrays.asList(columns));
if (set2.isEmpty()) {
needsToReadFromScanIndex = false;
}
}
}
long rc;
if (isScanIndex) {
rc = rowsCost + sortingCost + 20;
} else if (needsToReadFromScanIndex) {
rc = rowsCost + rowsCost + sortingCost + 20;
} else {
/*
* The (20-x) calculation makes sure that when we pick a covering
* index, we pick the covering index that has the smallest number of
* columns. This is faster because a smaller index will fit into
* fewer data blocks.
*/
rc = rowsCost + sortingCost + (20 - columns.length);
}
return rc;
}
@Override
public int compareRows(SearchRow rowData, SearchRow compare) {
if (rowData == compare) {
......
......@@ -15,7 +15,8 @@ import org.h2.result.SearchRow;
* method {@link #isBatchFull()}} will return {@code true} or there are no more
* search rows to add. Then method {@link #find()} will be called to execute batched lookup.
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups.
* sequential batched lookups, moreover it can be reused for multiple queries for
* the same prepared statement.
*
* @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin
......@@ -26,9 +27,11 @@ public interface IndexLookupBatch {
*
* @param first the first row, or null for no limit
* @param last the last row, or null for no limit
* @return {@code false} if this search row pair is known to produce no results
* and thus the given row pair was not added
* @see Index#find(TableFilter, SearchRow, SearchRow)
*/
void addSearchRows(SearchRow first, SearchRow last);
boolean addSearchRows(SearchRow first, SearchRow last);
/**
* Check if this batch is full.
......@@ -47,4 +50,18 @@ public interface IndexLookupBatch {
* @return List of future cursors for collected search rows.
*/
List<Future<Cursor>> find();
/**
* Get plan for EXPLAIN.
*
* @return plan
*/
String getPlanSQL();
/**
* Reset this batch to clear state. This method will be called before and after each query execution.
*
* @param beforeQuery if it is being called before query execution
*/
void reset(boolean beforeQuery);
}
......@@ -144,7 +144,7 @@ public class LinkedIndex extends BaseIndex {
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return 100 + getCostRangeIndex(masks, rowCount +
Constants.COST_ROW_OFFSET, filters, filter, sortOrder);
Constants.COST_ROW_OFFSET, filters, filter, sortOrder, false);
}
@Override
......
......@@ -58,7 +58,7 @@ public class MetaIndex extends BaseIndex {
return 10 * MetaTable.ROW_COUNT_APPROXIMATION;
}
return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION,
filters, filter, sortOrder);
filters, filter, sortOrder, false);
}
@Override
......
......@@ -6,8 +6,6 @@
package org.h2.index;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import org.h2.api.ErrorCode;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
......
......@@ -220,7 +220,7 @@ public class PageBtreeIndex extends PageIndex {
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, tableData.getRowCount(session),
filters, filter, sortOrder);
filters, filter, sortOrder, false);
}
@Override
......
......@@ -99,7 +99,7 @@ public class PageDelegateIndex extends PageIndex {
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session),
filters, filter, sortOrder);
filters, filter, sortOrder, false);
}
@Override
......
......@@ -178,12 +178,6 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
filter.getSession());
}
@Override
protected long getCostRangeIndex(int[] masks, long rowCount,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return getCostRangeIndex(masks, rowCount, columns);
}
/**
* Compute spatial index cost
* @param masks Search mask
......@@ -210,10 +204,10 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return getCostRangeIndex(masks, table.getRowCountApproximation(),
filters, filter, sortOrder);
return getCostRangeIndex(masks, table.getRowCountApproximation(), columns);
}
@Override
public void remove(Session session) {
if (!treeMap.isClosed()) {
......
......@@ -321,7 +321,7 @@ public class TreeIndex extends BaseIndex {
public double getCost(Session session, int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder) {
return getCostRangeIndex(masks, tableData.getRowCountApproximation(),
filters, filter, sortOrder);
filters, filter, sortOrder, false);
}
@Override
......
......@@ -24,7 +24,7 @@ public class ViewCursor implements Cursor {
private final SearchRow first, last;
private Row current;
ViewCursor(ViewIndex index, LocalResult result, SearchRow first,
public ViewCursor(ViewIndex index, LocalResult result, SearchRow first,
SearchRow last) {
this.table = index.getTable();
this.index = index;
......
......@@ -91,7 +91,7 @@ public class MVDelegateIndex extends BaseIndex implements MVIndex {
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCountApproximation(),
filters, filter, sortOrder);
filters, filter, sortOrder, true);
}
@Override
......
......@@ -11,7 +11,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.index.BaseIndex;
......@@ -219,8 +218,8 @@ public class MVPrimaryIndex extends BaseIndex {
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
try {
long cost = 10 * (dataMap.sizeAsLongMax() + Constants.COST_ROW_OFFSET);
return cost;
return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
filters, filter, sortOrder, true);
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
}
......
......@@ -355,7 +355,7 @@ public class MVSecondaryIndex extends BaseIndex implements MVIndex {
TableFilter[] filters, int filter, SortOrder sortOrder) {
try {
return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
filters, filter, sortOrder);
filters, filter, sortOrder, false);
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
}
......
......@@ -239,14 +239,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return getCostRangeIndex(masks, table.getRowCountApproximation(),
filters, filter, sortOrder);
}
@Override
protected long getCostRangeIndex(int[] masks, long rowCount,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return SpatialTreeIndex.getCostRangeIndex(masks, rowCount, columns);
return SpatialTreeIndex.getCostRangeIndex(masks, table.getRowCountApproximation(), columns);
}
@Override
......
......@@ -12,7 +12,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
......@@ -46,6 +45,7 @@ import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid;
/**
......@@ -133,20 +133,13 @@ public class ValueDataType implements DataType {
if (aNull || bNull) {
return SortOrder.compareNull(aNull, sortType);
}
int comp = compareTypeSafe(a, b);
int comp = a.compareTypeSafe(b, compareMode);
if ((sortType & SortOrder.DESCENDING) != 0) {
comp = -comp;
}
return comp;
}
private int compareTypeSafe(Value a, Value b) {
if (a == b) {
return 0;
}
return a.compareTypeSafe(b, compareMode);
}
@Override
public int getMemory(Object obj) {
if (obj instanceof SpatialKey) {
......@@ -284,6 +277,12 @@ public class ValueDataType implements DataType {
putVarLong(nanos);
break;
}
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
long dateTimeValue = ts.getUtcDateTimeNanos();
buff.put((byte) type).putVarLong(dateTimeValue);
break;
}
case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy();
buff.put((byte) type).
......@@ -489,6 +488,10 @@ public class ValueDataType implements DataType {
long nanos = readVarLong(buff) * 1000000 + readVarLong(buff);
return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
}
case Value.TIMESTAMP_UTC: {
long dateTimeValue = readVarLong(buff);
return ValueTimestampUtc.fromNanos(dateTimeValue);
}
case Value.BYTES: {
int len = readVarInt(buff);
byte[] b = DataUtils.newBytes(len);
......
......@@ -113,7 +113,7 @@
90084=Kann das letzte Feld nicht löschen {0}
90085=Index {0} gehört zur Bedingung {1}
90086=Klasse {0} nicht gefunden
90087=Methode {0} nicht gefunden
90087=Method {0} with matching arguments not found
90088=Unbekannter Modus {0}
90089=Textvergleich-Modus kann nicht geändert werden wenn eine Daten-Tabelle existiert : {0}
90090=Schema {0} kann nicht gelöscht werden
......
......@@ -113,7 +113,7 @@
90084=Imposible eliminar la ultima columna {0}
90085=Index {0} pertenece a un constraint {1}
90086=Class {0} no encontrada
90087=Method {0} no encontrado
90087=Method {0} with matching arguments not found
90088=Modo desconocido {0}
90089=Collation no puede ser cambiado debido a que existe una tabla de datos: {0}
90090=Schema {0} no puede ser eliminado
......
......@@ -113,7 +113,7 @@
90084=Nie można skasować ostatniej kolumny {0}
90085=Indeks {0} należy do ograniczenia {1}
90086=Klasa {0} nie istnieje
90087=Metoda {0} nie istnieje
90087=Method {0} with matching arguments not found
90088=Nieznany stan {0}
90089=Metoda porównywania językowego nie może być zmieniona z powodu istnienia danych w tabeli {0}
90090=Schemat {0} nie może zostać skasowany
......
......@@ -113,7 +113,7 @@
90084=Não pode apagar a última coluna {0}
90085=índice {0} pertence a uma restrição {1}
90086=Classe {0} não foi encontrada
90087=Método {0} não foi encontrado
90087=Method {0} with matching arguments not found
90088=Modo {0} desconhecido
90089=A coleção não pode ser alterada, porque existe uma tabela de dados: {0}
90090=Esquema {0} não pode ser apagado
......
......@@ -17,7 +17,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
......@@ -50,6 +49,7 @@ import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid;
/**
......@@ -538,6 +538,12 @@ public class Data {
}
break;
}
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
writeByte((byte) type);
writeVarLong(ts.getUtcDateTimeNanos());
break;
}
case Value.GEOMETRY:
case Value.JAVA_OBJECT: {
writeByte((byte) type);
......@@ -772,6 +778,9 @@ public class Data {
DateTimeUtils.getTimeUTCWithoutDst(readVarLong()),
readVarInt());
}
case Value.TIMESTAMP_UTC: {
return ValueTimestampUtc.fromNanos(readVarLong());
}
case Value.BYTES: {
int len = readVarInt();
byte[] b = DataUtils.newBytes(len);
......@@ -1020,6 +1029,10 @@ public class Data {
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) +
getVarIntLen(ts.getNanos() % 1000000);
}
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
return 1 + getVarLongLen(ts.getUtcDateTimeNanos());
}
case Value.GEOMETRY:
case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy();
......
......@@ -6,7 +6,6 @@
package org.h2.table;
import java.sql.ResultSetMetaData;
import org.h2.api.ErrorCode;
import org.h2.command.Parser;
import org.h2.engine.Constants;
......@@ -32,6 +31,7 @@ import org.h2.value.ValueNull;
import org.h2.value.ValueString;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid;
/**
......@@ -294,6 +294,8 @@ public class Column {
value = ValueInt.get(0).convertTo(type);
} else if (dt.type == Value.TIMESTAMP) {
value = ValueTimestamp.fromMillis(session.getTransactionStart());
} else if (dt.type == Value.TIMESTAMP_UTC) {
value = ValueTimestampUtc.fromMillis(session.getTransactionStart());
} else if (dt.type == Value.TIME) {
value = ValueTime.fromNanos(0);
} else if (dt.type == Value.DATE) {
......@@ -399,7 +401,7 @@ public class Column {
*/
public void prepareExpression(Session session) {
if (defaultExpression != null) {
computeTableFilter = new TableFilter(session, table, null, false, null);
computeTableFilter = new TableFilter(session, table, null, false, null, 0);
defaultExpression.mapColumns(computeTableFilter, 0);
defaultExpression = defaultExpression.optimize(session);
}
......
差异被折叠。
......@@ -6,10 +6,12 @@
package org.h2.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.Trace;
import org.h2.table.TableFilter.TableFilterVisitor;
import org.h2.util.New;
......@@ -104,12 +106,23 @@ public class Plan {
* @return the cost
*/
public double calculateCost(Session session) {
Trace t = session.getTrace();
if (t.isDebugEnabled()) {
t.debug("Plan : calculate cost for plan {0}", Arrays.toString(allFilters));
}
double cost = 1;
boolean invalidPlan = false;
for (int i = 0; i < allFilters.length; i++) {
TableFilter tableFilter = allFilters[i];
if (t.isDebugEnabled()) {
t.debug("Plan : for table filter {0}", tableFilter);
}
PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i);
planItems.put(tableFilter, item);
if (t.isDebugEnabled()) {
t.debug("Plan : best plan item cost {0} index {1}",
item.cost, item.getIndex().getPlanSQL());
}
cost += cost * item.cost;
setEvaluatable(tableFilter, true);
Expression on = tableFilter.getJoinCondition();
......@@ -123,6 +136,9 @@ public class Plan {
if (invalidPlan) {
cost = Double.POSITIVE_INFINITY;
}
if (t.isDebugEnabled()) {
session.getTrace().debug("Plan : plan cost {0}", cost);
}
for (TableFilter f : allFilters) {
setEvaluatable(f, false);
}
......
......@@ -19,7 +19,6 @@ public class SubQueryInfo {
private TableFilter[] filters;
private int filter;
private SortOrder sortOrder;
private boolean preliminary;
private SubQueryInfo upper;
/**
......@@ -28,17 +27,14 @@ public class SubQueryInfo {
* @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) {
SortOrder sortOrder) {
this.upper = upper;
this.masks = masks;
this.filters = filters;
this.filter = filter;
this.sortOrder = sortOrder;
this.preliminary = preliminary;
}
public SubQueryInfo getUpper() {
......@@ -60,8 +56,4 @@ public class SubQueryInfo {
public SortOrder getSortOrder() {
return sortOrder;
}
public boolean isPreliminary() {
return preliminary;
}
}
......@@ -127,6 +127,10 @@ public abstract class Table extends SchemaObjectBase {
}
}
public boolean isView() {
return false;
}
/**
* Lock the table for the given session.
* This method waits until the lock is granted.
......@@ -693,11 +697,20 @@ public abstract class Table extends SchemaObjectBase {
PlanItem item = new PlanItem();
item.setIndex(getScanIndex(session));
item.cost = item.getIndex().getCost(session, null, filters, filter, null);
Trace t = session.getTrace();
if (t.isDebugEnabled()) {
t.debug("Table : potential plan item cost {0} index {1}",
item.cost, item.getIndex().getPlanSQL());
}
ArrayList<Index> indexes = getIndexes();
if (indexes != null && masks != null) {
for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i);
double cost = index.getCost(session, masks, filters, filter, sortOrder);
if (t.isDebugEnabled()) {
t.debug("Table : potential plan item cost {0} index {1}",
cost, index.getPlanSQL());
}
if (cost < item.cost) {
item.cost = cost;
item.setIndex(index);
......
......@@ -218,6 +218,11 @@ public class TableView extends Table {
}
}
@Override
public boolean isView() {
return true;
}
/**
* Check if this view is currently invalid.
*
......@@ -230,15 +235,15 @@ public class TableView extends Table {
@Override
public PlanItem getBestPlanItem(Session session, int[] masks,
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, this);
Map<Object, ViewIndex> indexCache = session.getViewIndexCache(topQuery != null);
ViewIndex i = indexCache.get(cacheKey);
if (i == null) {
if (i == null || i.isExpired()) {
i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder);
indexCache.put(cacheKey, i);
}
PlanItem item = new PlanItem();
item.cost = i.getCost(session, masks, filters, filter, sortOrder);
item.setIndex(i);
return item;
}
......
/*
* 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.util;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.h2.message.DbException;
/**
* Single threaded lazy future.
*
* @param <T>
* @author Sergi Vladykin
*/
public abstract class LazyFuture<T> implements Future<T> {
private static final int S_READY = 0;
private static final int S_DONE = 1;
private static final int S_ERROR = 2;
private static final int S_CANCELED = 3;
private int state = S_READY;
private T result;
private Exception error;
/**
* Reset this future to the initial state.
*
* @return {@code false} if it was already in initial state
*/
public boolean reset() {
if (state == S_READY) {
return false;
}
state = S_READY;
result = null;
error = null;
return true;
}
/**
* Run computation and produce the result.
*
* @return the result of computation
*/
protected abstract T run() throws Exception;
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (state != S_READY) {
return false;
}
state = S_CANCELED;
return true;
}
@Override
public T get() throws InterruptedException, ExecutionException {
switch (state) {
case S_READY:
try {
result = run();
state = S_DONE;
} catch (Exception e) {
error = e;
if (e instanceof InterruptedException) {
throw (InterruptedException) e;
}
throw new ExecutionException(e);
} finally {
if (state != S_DONE) {
state = S_ERROR;
}
}
return result;
case S_DONE:
return result;
case S_ERROR:
throw new ExecutionException(error);
case S_CANCELED:
throw new CancellationException();
default:
throw DbException.throwInternalError();
}
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException {
return get();
}
@Override
public boolean isCancelled() {
return state == S_CANCELED;
}
@Override
public boolean isDone() {
return state != S_READY;
}
}
......@@ -22,7 +22,6 @@ import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SessionInterface;
......@@ -315,6 +314,13 @@ public class DataType {
// 24 for ValueTimestamp, 32 for java.sql.Timestamp
56
);
add(Value.TIMESTAMP_UTC, Types.TIMESTAMP, "TimestampUtc",
createDate(ValueTimestamp.PRECISION, "TIMESTAMP_UTC",
ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.DISPLAY_SIZE),
new String[]{"TIMESTAMP_UTC"},
// 24 for ValueTimestampUtc, 32 for java.sql.Timestamp
56
);
add(Value.BYTES, Types.VARBINARY, "Bytes",
createString(false),
new String[]{"VARBINARY"},
......@@ -539,6 +545,12 @@ public class DataType {
ValueTimestamp.get(value);
break;
}
case Value.TIMESTAMP_UTC: {
Timestamp value = rs.getTimestamp(columnIndex);
v = value == null ? (Value) ValueNull.INSTANCE :
ValueTimestampUtc.fromMillisNanos(value.getTime(), value.getNanos());
break;
}
case Value.DECIMAL: {
BigDecimal value = rs.getBigDecimal(columnIndex);
v = value == null ? (Value) ValueNull.INSTANCE :
......@@ -711,6 +723,7 @@ public class DataType {
// "java.sql.Date";
return Date.class.getName();
case Value.TIMESTAMP:
case Value.TIMESTAMP_UTC:
// "java.sql.Timestamp";
return Timestamp.class.getName();
case Value.BYTES:
......
......@@ -18,7 +18,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SessionInterface;
......@@ -388,6 +387,11 @@ public class Transfer {
}
break;
}
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
writeLong(ts.getUtcDateTimeNanos());
break;
}
case Value.DECIMAL:
writeString(v.getString());
break;
......@@ -574,6 +578,9 @@ public class Transfer {
return ValueTimestamp.fromMillisNanos(readLong(),
readInt() % 1000000);
}
case Value.TIMESTAMP_UTC: {
return ValueTimestampUtc.fromNanos(readLong());
}
case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(readString()));
case Value.DOUBLE:
......
......@@ -18,7 +18,6 @@ import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
......@@ -159,11 +158,15 @@ public abstract class Value {
* The value type for string values with a fixed size.
*/
public static final int GEOMETRY = 22;
/**
* The value type for TIMESTAMP UTC values.
*/
public static final int TIMESTAMP_UTC = 23;
/**
* The number of value types.
*/
public static final int TYPE_COUNT = GEOMETRY + 1;
public static final int TYPE_COUNT = TIMESTAMP_UTC + 1;
private static SoftReference<Value[]> softCache =
new SoftReference<Value[]>(null);
......@@ -299,6 +302,8 @@ public abstract class Value {
return 31;
case TIMESTAMP:
return 32;
case TIMESTAMP_UTC:
return 33;
case BYTES:
return 40;
case BLOB:
......@@ -542,6 +547,7 @@ public abstract class Value {
case TIME:
case DATE:
case TIMESTAMP:
case TIMESTAMP_UTC:
case BYTES:
case JAVA_OBJECT:
case UUID:
......@@ -559,6 +565,7 @@ public abstract class Value {
case INT:
return ValueByte.get(convertToByte(getInt()));
case LONG:
case TIMESTAMP_UTC:
return ValueByte.get(convertToByte(getLong()));
case DECIMAL:
return ValueByte.get(convertToByte(convertToLong(getBigDecimal())));
......@@ -580,6 +587,7 @@ public abstract class Value {
case INT:
return ValueShort.get(convertToShort(getInt()));
case LONG:
case TIMESTAMP_UTC:
return ValueShort.get(convertToShort(getLong()));
case DECIMAL:
return ValueShort.get(convertToShort(convertToLong(getBigDecimal())));
......@@ -601,6 +609,7 @@ public abstract class Value {
case SHORT:
return ValueInt.get(getShort());
case LONG:
case TIMESTAMP_UTC:
return ValueInt.get(convertToInt(getLong()));
case DECIMAL:
return ValueInt.get(convertToInt(convertToLong(getBigDecimal())));
......@@ -637,6 +646,8 @@ public abstract class Value {
}
return ValueLong.get(Long.parseLong(getString(), 16));
}
case TIMESTAMP_UTC:
return ValueLong.get(getLong());
}
break;
}
......@@ -652,6 +663,7 @@ public abstract class Value {
case INT:
return ValueDecimal.get(BigDecimal.valueOf(getInt()));
case LONG:
case TIMESTAMP_UTC:
return ValueDecimal.get(BigDecimal.valueOf(getLong()));
case DOUBLE: {
double d = getDouble();
......@@ -684,6 +696,7 @@ public abstract class Value {
case INT:
return ValueDouble.get(getInt());
case LONG:
case TIMESTAMP_UTC:
return ValueDouble.get(getLong());
case DECIMAL:
return ValueDouble.get(getBigDecimal().doubleValue());
......@@ -703,6 +716,7 @@ public abstract class Value {
case INT:
return ValueFloat.get(getInt());
case LONG:
case TIMESTAMP_UTC:
return ValueFloat.get(getLong());
case DECIMAL:
return ValueFloat.get(getBigDecimal().floatValue());
......@@ -721,6 +735,9 @@ public abstract class Value {
case TIMESTAMP:
return ValueDate.fromDateValue(
((ValueTimestamp) this).getDateValue());
case TIMESTAMP_UTC:
return ValueDate.fromMillis(
((ValueTimestampUtc) this).getUtcDateTimeMillis());
}
break;
}
......@@ -733,6 +750,9 @@ public abstract class Value {
case TIMESTAMP:
return ValueTime.fromNanos(
((ValueTimestamp) this).getTimeNanos());
case TIMESTAMP_UTC:
return ValueTime.fromMillis(
((ValueTimestampUtc) this).getUtcDateTimeMillis());
}
break;
}
......@@ -744,6 +764,35 @@ public abstract class Value {
case DATE:
return ValueTimestamp.fromDateValueAndNanos(
((ValueDate) this).getDateValue(), 0);
case TIMESTAMP_UTC:
return ValueTimestamp.fromMillisNanos(
((ValueTimestampUtc) this).getUtcDateTimeMillis(),
((ValueTimestampUtc) this).getNanosSinceLastMilli());
}
break;
}
case TIMESTAMP_UTC: {
switch (getType()) {
case BOOLEAN:
return ValueTimestampUtc.fromNanos(getBoolean().booleanValue() ? 1 : 0);
case BYTE:
return ValueTimestampUtc.fromNanos(getByte());
case SHORT:
return ValueTimestampUtc.fromNanos(getShort());
case INT:
return ValueTimestampUtc.fromNanos(getInt());
case LONG:
return ValueTimestampUtc.fromNanos(getLong());
case DECIMAL:
return ValueTimestampUtc.fromNanos(getBigDecimal().longValue());
case FLOAT:
return ValueTimestampUtc.fromNanos((long) getFloat());
case DOUBLE:
return ValueTimestampUtc.fromNanos((long) getDouble());
case TIMESTAMP:
return ValueTimestampUtc.fromMillisNanos(
((ValueTimestamp) this).getTimestamp().getTime(),
((ValueTimestamp) this).getTimestamp().getNanos());
}
break;
}
......@@ -860,6 +909,8 @@ public abstract class Value {
return ValueDate.parse(s.trim());
case TIMESTAMP:
return ValueTimestamp.parse(s.trim());
case TIMESTAMP_UTC:
return ValueTimestampUtc.parse(s.trim());
case BYTES:
return ValueBytes.getNoCopy(
StringUtils.convertHexToBytes(s.trim()));
......
/*
* 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.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.h2.message.DbException;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/**
* Implementation of the TIMESTAMP data type.
*/
public final class ValueTimestampUtc extends Value {
/**
* The precision in digits.
*/
public static final int PRECISION = 23;
/**
* The display size of the textual representation of a timestamp. Example:
* 2001-01-01 23:59:59.000 UTC
*/
static final int DISPLAY_SIZE = 27;
/**
* The default scale for timestamps.
*/
static final int DEFAULT_SCALE = 10;
/**
* Time in nanoseconds since 1 Jan 1970 i.e. similar format to
* System.currentTimeMillis()
*/
private final long utcDateTimeNanos;
private ValueTimestampUtc(long utcDateTimeNanos) {
this.utcDateTimeNanos = utcDateTimeNanos;
}
/**
* Get or create a timestamp value for the given date/time in millis.
*
* @param utcDateTimeMillis the date and time in UTC milliseconds
* @param nanos the nanoseconds since the last millisecond
* @return the value
*/
public static ValueTimestampUtc fromMillisNanos(long utcDateTimeMillis, int nanos) {
if (nanos < 0 || nanos >= 1000 * 1000) {
throw new IllegalArgumentException("nanos out of range " + nanos);
}
return (ValueTimestampUtc) Value.cache(new ValueTimestampUtc(utcDateTimeMillis * 1000 * 1000 + nanos));
}
/**
* Get or create a timestamp value for the given date/time in millis.
*
* @param ms the milliseconds
* @return the value
*/
public static ValueTimestampUtc fromMillis(long ms) {
return fromMillisNanos(ms, (short) 0);
}
/**
* Get or create a timestamp value for the given date/time in nanos.
*
* @param nanos the nanos
* @return the value
*/
public static ValueTimestampUtc fromNanos(long nanos) {
return (ValueTimestampUtc) Value.cache(new ValueTimestampUtc(nanos));
}
/**
* Parse a string to a ValueTimestamp. This method supports the format
* +/-year-month-day hour:minute:seconds.fractional and an optional timezone
* part.
*
* @param s the string to parse
* @return the date
*/
public static ValueTimestampUtc parse(String s) {
ValueTimestamp t1 = ValueTimestamp.parse(s);
java.sql.Timestamp t2 = t1.getTimestamp();
return fromMillisNanos(t2.getTime(), t2.getNanos());
}
/**
* Time in nanoseconds since 1 Jan 1970 i.e. similar format to
* System.currentTimeMillis()
*/
public long getUtcDateTimeNanos() {
return utcDateTimeNanos;
}
/**
* Time in milliseconds since 1 Jan 1970 i.e. same format as
* System.currentTimeMillis()
*/
public long getUtcDateTimeMillis() {
return utcDateTimeNanos / 1000 / 1000;
}
public int getNanosSinceLastMilli() {
return (int) (utcDateTimeNanos % (1000 * 1000));
}
@Override
public java.sql.Timestamp getTimestamp() {
java.sql.Timestamp ts = new java.sql.Timestamp(getUtcDateTimeMillis());
ts.setNanos(getNanosSinceLastMilli());
return ts;
}
@Override
public int getType() {
return Value.TIMESTAMP_UTC;
}
@Override
public String getString() {
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
cal.setTimeInMillis(getUtcDateTimeMillis());
StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
// date part
int y = cal.get(Calendar.YEAR);
int m = cal.get(Calendar.MONTH);
int d = cal.get(Calendar.DAY_OF_MONTH);
if (y > 0 && y < 10000) {
StringUtils.appendZeroPadded(buff, 4, y);
} else {
buff.append(y);
}
buff.append('-');
StringUtils.appendZeroPadded(buff, 2, m);
buff.append('-');
StringUtils.appendZeroPadded(buff, 2, d);
buff.append(' ');
// time part
long timeNanos = cal.get(Calendar.HOUR_OF_DAY);
timeNanos *= 24;
timeNanos += cal.get(Calendar.MINUTE);
timeNanos *= 60;
timeNanos += cal.get(Calendar.SECOND);
timeNanos *= 60;
timeNanos += cal.get(Calendar.MILLISECOND);
timeNanos *= 1000 * 1000;
timeNanos += getNanosSinceLastMilli();
ValueTime.appendTime(buff, timeNanos, true);
buff.append(" UTC");
return buff.toString();
}
@Override
public String getSQL() {
return "TIMESTAMP UTC '" + getString() + "'";
}
@Override
public long getPrecision() {
return PRECISION;
}
@Override
public int getScale() {
return DEFAULT_SCALE;
}
@Override
public int getDisplaySize() {
return DISPLAY_SIZE;
}
@Override
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale >= DEFAULT_SCALE) {
return this;
}
if (targetScale < 0) {
throw DbException.getInvalidValueException("scale", targetScale);
} /*
* TODO long n = timeNanos; BigDecimal bd = BigDecimal.valueOf(n); bd
* = bd.movePointLeft(9); bd = ValueDecimal.setScale(bd, targetScale);
* bd = bd.movePointRight(9); long n2 = bd.longValue(); if (n2 == n) {
* return this; }
*/
return this;
}
@Override
protected int compareSecure(Value o, CompareMode mode) {
ValueTimestampUtc t = (ValueTimestampUtc) o;
return MathUtils.compareLong(utcDateTimeNanos, t.utcDateTimeNanos);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof ValueTimestampUtc)) {
return false;
}
ValueTimestampUtc x = (ValueTimestampUtc) other;
return utcDateTimeNanos == x.utcDateTimeNanos;
}
@Override
public int hashCode() {
return (int) (utcDateTimeNanos ^ (utcDateTimeNanos >>> 32));
}
@Override
public Object getObject() {
return getTimestamp();
}
@Override
public long getLong() {
return utcDateTimeNanos;
}
@Override
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setTimestamp(parameterIndex, getTimestamp());
}
@Override
public Value add(Value v) {
ValueTimestampUtc t = (ValueTimestampUtc) v.convertTo(Value.TIMESTAMP_UTC);
long d1 = utcDateTimeNanos + t.utcDateTimeNanos;
return new ValueTimestampUtc(d1);
}
@Override
public Value subtract(Value v) {
ValueTimestampUtc t = (ValueTimestampUtc) v.convertTo(Value.TIMESTAMP_UTC);
long d1 = utcDateTimeNanos - t.utcDateTimeNanos;
return new ValueTimestampUtc(d1);
}
}
......@@ -80,10 +80,10 @@ public class TestSelectCountNonNullColumn extends TestBase {
if (expect >= 0) {
assertEquals(expect, rs.getLong(1));
} else {
// System.out.println(rs.getString(1));
assertEquals("SELECT\n" + " COUNT(KEY)\n"
assertEquals("SELECT\n"
+ " COUNT(KEY)\n"
+ "FROM PUBLIC.SIMPLE\n"
+ " /* PUBLIC.SIMPLE.tableScan */\n"
+ " /* PUBLIC.PRIMARY_KEY_9 */\n"
+ "/* direct lookup */", rs.getString(1));
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论