提交 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 [,...] ...@@ -7,7 +7,7 @@ SELECT [ TOP term ] [ DISTINCT | ALL ] selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ] FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ] [ GROUP BY expression [,...] ] [ HAVING expression ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ] [ ORDER BY order [,...] ] [ { 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 ] [ FOR UPDATE ]
"," ","
Selects data from a table or multiple tables. Selects data from a table or multiple tables.
......
...@@ -1588,6 +1588,8 @@ The following file systems are included: ...@@ -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>. 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>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>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> </li></ul>
<p> <p>
As an example, to use the the <code>nio</code> file system, use the following database URL: As an example, to use the the <code>nio</code> file system, use the following database URL:
......
...@@ -21,6 +21,14 @@ Change Log ...@@ -21,6 +21,14 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <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>CLOB and BLOB objects of removed rows were sometimes kept in the database file.
</li> </li>
<li>Server mode: executing "shutdown" left a thread on the server. <li>Server mode: executing "shutdown" left a thread on the server.
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
90084=Cannot drop last column {0} 90084=Cannot drop last column {0}
90085=Index {0} belongs to constraint {1} 90085=Index {0} belongs to constraint {1}
90086=Class {0} not found 90086=Class {0} not found
90087=Method {0} not found 90087=Method {0} with matching arguments not found
90088=Unknown mode {0} 90088=Unknown mode {0}
90089=Collation cannot be changed because there is a data table: {0} 90089=Collation cannot be changed because there is a data table: {0}
90090=Schema {0} cannot be dropped 90090=Schema {0} cannot be dropped
......
...@@ -1358,7 +1358,7 @@ public class ErrorCode { ...@@ -1358,7 +1358,7 @@ public class ErrorCode {
/** /**
* The error with code <code>90087</code> is thrown when * 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: * Example:
* <pre> * <pre>
* CREATE ALIAS TO_BINARY FOR "java.lang.Long.toBinaryString(long)"; * CREATE ALIAS TO_BINARY FOR "java.lang.Long.toBinaryString(long)";
......
...@@ -68,6 +68,11 @@ public abstract class Command implements CommandInterface { ...@@ -68,6 +68,11 @@ public abstract class Command implements CommandInterface {
@Override @Override
public abstract boolean isQuery(); public abstract boolean isQuery();
/**
* Prepare join batching.
*/
public abstract void prepareJoinBatch();
/** /**
* Get the list of parameters. * Get the list of parameters.
* *
......
...@@ -7,6 +7,8 @@ package org.h2.command; ...@@ -7,6 +7,8 @@ package org.h2.command;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.api.DatabaseEventListener; 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.Parameter;
import org.h2.expression.ParameterInterface; import org.h2.expression.ParameterInterface;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
...@@ -44,6 +46,23 @@ public class CommandContainer extends Command { ...@@ -44,6 +46,23 @@ public class CommandContainer extends Command {
return prepared.isQuery(); 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() { private void recompileIfRequired() {
if (prepared.needRecompile()) { if (prepared.needRecompile()) {
// TODO test with 'always recompile' // TODO test with 'always recompile'
...@@ -65,6 +84,7 @@ public class CommandContainer extends Command { ...@@ -65,6 +84,7 @@ public class CommandContainer extends Command {
} }
prepared.prepare(); prepared.prepare();
prepared.setModificationMetaId(mod); prepared.setModificationMetaId(mod);
prepareJoinBatch();
} }
} }
......
...@@ -44,6 +44,11 @@ class CommandList extends Command { ...@@ -44,6 +44,11 @@ class CommandList extends Command {
return updateCount; return updateCount;
} }
@Override
public void prepareJoinBatch() {
command.prepareJoinBatch();
}
@Override @Override
public ResultInterface query(int maxrows) { public ResultInterface query(int maxrows) {
ResultInterface result = command.query(maxrows); ResultInterface result = command.query(maxrows);
......
...@@ -13,6 +13,8 @@ import java.math.BigInteger; ...@@ -13,6 +13,8 @@ import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
...@@ -177,6 +179,14 @@ public class Parser { ...@@ -177,6 +179,14 @@ public class Parser {
CURRENT_TIME = 23, ROWNUM = 24; CURRENT_TIME = 23, ROWNUM = 24;
private static final int SPATIAL_INTERSECTS = 25; 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 Database database;
private final Session session; private final Session session;
/** /**
...@@ -208,6 +218,7 @@ public class Parser { ...@@ -208,6 +218,7 @@ public class Parser {
private boolean rightsChecked; private boolean rightsChecked;
private boolean recompileAlways; private boolean recompileAlways;
private ArrayList<Parameter> indexedParameterList; private ArrayList<Parameter> indexedParameterList;
private int orderInFrom;
public Parser(Session session) { public Parser(Session session) {
this.database = session.getDatabase(); this.database = session.getDatabase();
...@@ -704,7 +715,7 @@ public class Parser { ...@@ -704,7 +715,7 @@ public class Parser {
Update command = new Update(session); Update command = new Update(session);
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
TableFilter filter = readSimpleTableFilter(); TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter); command.setTableFilter(filter);
read("SET"); read("SET");
if (readIf("(")) { if (readIf("(")) {
...@@ -760,7 +771,7 @@ public class Parser { ...@@ -760,7 +771,7 @@ public class Parser {
return command; return command;
} }
private TableFilter readSimpleTableFilter() { private TableFilter readSimpleTableFilter(int orderInFrom) {
Table table = readTableOrView(); Table table = readTableOrView();
String alias = null; String alias = null;
if (readIf("AS")) { if (readIf("AS")) {
...@@ -772,7 +783,7 @@ public class Parser { ...@@ -772,7 +783,7 @@ public class Parser {
} }
} }
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
currentSelect); currentSelect, orderInFrom);
} }
private Delete parseDelete() { private Delete parseDelete() {
...@@ -784,7 +795,7 @@ public class Parser { ...@@ -784,7 +795,7 @@ public class Parser {
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
readIf("FROM"); readIf("FROM");
TableFilter filter = readSimpleTableFilter(); TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter); command.setTableFilter(filter);
if (readIf("WHERE")) { if (readIf("WHERE")) {
Expression condition = readExpression(); Expression condition = readExpression();
...@@ -1186,7 +1197,7 @@ public class Parser { ...@@ -1186,7 +1197,7 @@ public class Parser {
return top; return top;
} }
} else if (readIf("VALUES")) { } else if (readIf("VALUES")) {
table = parseValuesTable().getTable(); table = parseValuesTable(0).getTable();
} else { } else {
String tableName = readIdentifierWithSchema(null); String tableName = readIdentifierWithSchema(null);
Schema schema = getSchema(); Schema schema = getSchema();
...@@ -1236,7 +1247,7 @@ public class Parser { ...@@ -1236,7 +1247,7 @@ public class Parser {
} }
alias = readFromAlias(alias); alias = readFromAlias(alias);
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
currentSelect); currentSelect, orderInFrom++);
} }
private String readFromAlias(String alias) { private String readFromAlias(String alias) {
...@@ -1610,7 +1621,7 @@ public class Parser { ...@@ -1610,7 +1621,7 @@ public class Parser {
private TableFilter getNested(TableFilter n) { private TableFilter getNested(TableFilter n) {
String joinTable = Constants.PREFIX_JOIN + parseIndex; String joinTable = Constants.PREFIX_JOIN + parseIndex;
TableFilter top = new TableFilter(session, getDualTable(true), TableFilter top = new TableFilter(session, getDualTable(true),
joinTable, rightsChecked, currentSelect); joinTable, rightsChecked, currentSelect, n.getOrderInFrom());
top.addJoin(n, false, true, null); top.addJoin(n, false, true, null);
return top; return top;
} }
...@@ -1774,7 +1785,7 @@ public class Parser { ...@@ -1774,7 +1785,7 @@ public class Parser {
if (readIf("OFFSET")) { if (readIf("OFFSET")) {
command.setOffset(readExpression().optimize(session)); command.setOffset(readExpression().optimize(session));
if (!readIf("ROW")) { if (!readIf("ROW")) {
read("ROWS"); readIf("ROWS");
} }
} }
if (readIf("FETCH")) { if (readIf("FETCH")) {
...@@ -1873,6 +1884,38 @@ public class Parser { ...@@ -1873,6 +1884,38 @@ public class Parser {
TableFilter filter = readTableFilter(false); TableFilter filter = readTableFilter(false);
parseJoinTableFilter(filter, command); parseJoinTableFilter(filter, command);
} while (readIf(",")); } 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) { private void parseJoinTableFilter(TableFilter top, final Select command) {
...@@ -1976,7 +2019,7 @@ public class Parser { ...@@ -1976,7 +2019,7 @@ public class Parser {
// SYSTEM_RANGE(1,1) // SYSTEM_RANGE(1,1)
Table dual = getDualTable(false); Table dual = getDualTable(false);
TableFilter filter = new TableFilter(session, dual, null, TableFilter filter = new TableFilter(session, dual, null,
rightsChecked, currentSelect); rightsChecked, currentSelect, 0);
command.addTableFilter(filter, true); command.addTableFilter(filter, true);
} else { } else {
parseSelectSimpleFromPart(command); parseSelectSimpleFromPart(command);
...@@ -4312,7 +4355,7 @@ public class Parser { ...@@ -4312,7 +4355,7 @@ public class Parser {
private Select parseValues() { private Select parseValues() {
Select command = new Select(session); Select command = new Select(session);
currentSelect = command; currentSelect = command;
TableFilter filter = parseValuesTable(); TableFilter filter = parseValuesTable(0);
ArrayList<Expression> list = New.arrayList(); ArrayList<Expression> list = New.arrayList();
list.add(new Wildcard(null, null)); list.add(new Wildcard(null, null));
command.setExpressions(list); command.setExpressions(list);
...@@ -4321,7 +4364,7 @@ public class Parser { ...@@ -4321,7 +4364,7 @@ public class Parser {
return command; return command;
} }
private TableFilter parseValuesTable() { private TableFilter parseValuesTable(int orderInFrom) {
Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN); Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
TableFunction tf = (TableFunction) Function.getFunction(database, TableFunction tf = (TableFunction) Function.getFunction(database,
"TABLE"); "TABLE");
...@@ -4397,7 +4440,7 @@ public class Parser { ...@@ -4397,7 +4440,7 @@ public class Parser {
tf.doneWithParameters(); tf.doneWithParameters();
Table table = new FunctionTable(mainSchema, session, tf, tf); Table table = new FunctionTable(mainSchema, session, tf, tf);
TableFilter filter = new TableFilter(session, table, null, TableFilter filter = new TableFilter(session, table, null,
rightsChecked, currentSelect); rightsChecked, currentSelect, orderInFrom);
return filter; return filter;
} }
......
...@@ -175,7 +175,7 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -175,7 +175,7 @@ public class AlterTableAddConstraint extends SchemaCommand {
int id = getObjectId(); int id = getObjectId();
String name = generateConstraintName(table); String name = generateConstraintName(table);
ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, 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.mapColumns(filter, 0);
checkExpression = checkExpression.optimize(session); checkExpression = checkExpression.optimize(session);
check.setExpression(checkExpression); check.setExpression(checkExpression);
...@@ -294,6 +294,9 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -294,6 +294,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
} }
private static Index getUniqueIndex(Table t, IndexColumn[] cols) { private static Index getUniqueIndex(Table t, IndexColumn[] cols) {
if (t.getIndexes() == null) {
return null;
}
for (Index idx : t.getIndexes()) { for (Index idx : t.getIndexes()) {
if (canUseUniqueIndex(idx, t, cols)) { if (canUseUniqueIndex(idx, t, cols)) {
return idx; return idx;
...@@ -303,6 +306,9 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -303,6 +306,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
} }
private static Index getIndex(Table t, IndexColumn[] cols, boolean moreColumnOk) { private static Index getIndex(Table t, IndexColumn[] cols, boolean moreColumnOk) {
if (t.getIndexes() == null) {
return null;
}
for (Index idx : t.getIndexes()) { for (Index idx : t.getIndexes()) {
if (canUseIndex(idx, t, cols, moreColumnOk)) { if (canUseIndex(idx, t, cols, moreColumnOk)) {
return idx; return idx;
......
...@@ -40,6 +40,10 @@ public class Explain extends Prepared { ...@@ -40,6 +40,10 @@ public class Explain extends Prepared {
this.command = command; this.command = command;
} }
public Prepared getCommand() {
return command;
}
@Override @Override
public void prepare() { public void prepare() {
command.prepare(); command.prepare();
......
...@@ -24,11 +24,6 @@ public class NoOperation extends Prepared { ...@@ -24,11 +24,6 @@ public class NoOperation extends Prepared {
return 0; return 0;
} }
@Override
public boolean isQuery() {
return false;
}
@Override @Override
public boolean isTransactional() { public boolean isTransactional() {
return true; return true;
......
...@@ -53,16 +53,6 @@ class Optimizer { ...@@ -53,16 +53,6 @@ class Optimizer {
this.session = session; 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 * How many filter to calculate using brute force. The remaining filters are
* selected using a greedy algorithm which has a runtime of (1 + 2 + ... + * selected using a greedy algorithm which has a runtime of (1 + 2 + ... +
...@@ -85,7 +75,7 @@ class Optimizer { ...@@ -85,7 +75,7 @@ class Optimizer {
private void calculateBestPlan() { private void calculateBestPlan() {
cost = -1; cost = -1;
if (filters.length == 1 || !isJoinReorderingEnabled()) { if (filters.length == 1 || session.isForceJoinOrder()) {
testPlan(filters); testPlan(filters);
} else { } else {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
...@@ -242,7 +232,7 @@ class Optimizer { ...@@ -242,7 +232,7 @@ 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. * @param parse If we do not need to really get the best plan because it is a view parsing stage.
*/ */
void optimize(boolean parse) { void optimize(boolean parse) {
if (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 { ...@@ -71,6 +71,18 @@ public abstract class Query extends Prepared {
super(session); 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, * 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 * the results are written to it, and the method returns null. If no target
......
...@@ -9,7 +9,6 @@ import java.util.ArrayList; ...@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
...@@ -36,6 +35,7 @@ import org.h2.result.SortOrder; ...@@ -36,6 +35,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.JoinBatch;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.New; import org.h2.util.New;
...@@ -87,6 +87,11 @@ public class Select extends Query { ...@@ -87,6 +87,11 @@ public class Select extends Query {
super(session); super(session);
} }
@Override
public boolean isUnion() {
return false;
}
/** /**
* Add a table to the query. * Add a table to the query.
* *
...@@ -634,18 +639,25 @@ public class Select extends Query { ...@@ -634,18 +639,25 @@ public class Select extends Query {
topTableFilter.lock(session, exclusive, exclusive); topTableFilter.lock(session, exclusive, exclusive);
ResultTarget to = result != null ? result : target; ResultTarget to = result != null ? result : target;
if (limitRows != 0) { if (limitRows != 0) {
if (isQuickAggregateQuery) { try {
queryQuick(columnCount, to); if (isQuickAggregateQuery) {
} else if (isGroupQuery) { queryQuick(columnCount, to);
if (isGroupSortedQuery) { } else if (isGroupQuery) {
queryGroupSorted(columnCount, to); if (isGroupSortedQuery) {
queryGroupSorted(columnCount, to);
} else {
queryGroup(columnCount, result);
}
} else if (isDistinctQuery) {
queryDistinct(to, limitRows);
} else { } 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) { if (offsetExpr != null) {
...@@ -945,6 +957,30 @@ public class Select extends Query { ...@@ -945,6 +957,30 @@ public class Select extends Query {
isPrepared = true; 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 @Override
public double getCost() { public double getCost() {
return cost; return cost;
......
...@@ -7,7 +7,6 @@ package org.h2.command.dml; ...@@ -7,7 +7,6 @@ package org.h2.command.dml;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
import org.h2.engine.Session; import org.h2.engine.Session;
...@@ -72,6 +71,17 @@ public class SelectUnion extends Query { ...@@ -72,6 +71,17 @@ public class SelectUnion extends Query {
this.left = query; this.left = query;
} }
@Override
public boolean isUnion() {
return true;
}
@Override
public void prepareJoinBatch() {
left.prepareJoinBatch();
right.prepareJoinBatch();
}
public void setUnionType(int type) { public void setUnionType(int type) {
this.unionType = type; this.unionType = type;
} }
......
...@@ -497,6 +497,24 @@ public class Set extends Prepared { ...@@ -497,6 +497,24 @@ public class Set extends Prepared {
database.setRowFactory(rowFactory); database.setRowFactory(rowFactory);
break; 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: default:
DbException.throwInternalError("type="+type); DbException.throwInternalError("type="+type);
} }
......
...@@ -228,6 +228,16 @@ public class SetTypes { ...@@ -228,6 +228,16 @@ public class SetTypes {
*/ */
public static final int ROW_FACTORY = 43; 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 static final ArrayList<String> TYPES = New.arrayList();
private SetTypes() { private SetTypes() {
...@@ -280,6 +290,8 @@ public class SetTypes { ...@@ -280,6 +290,8 @@ public class SetTypes {
list.add(QUERY_STATISTICS, "QUERY_STATISTICS"); list.add(QUERY_STATISTICS, "QUERY_STATISTICS");
list.add(QUERY_STATISTICS_MAX_ENTRIES, "QUERY_STATISTICS_MAX_ENTRIES"); list.add(QUERY_STATISTICS_MAX_ENTRIES, "QUERY_STATISTICS_MAX_ENTRIES");
list.add(ROW_FACTORY, "ROW_FACTORY"); 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 { ...@@ -171,6 +171,10 @@ public class Update extends Prepared {
if (condition != null) { if (condition != null) {
buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL())); buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL()));
} }
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
}
return buff.toString(); return buff.toString();
} }
......
...@@ -17,6 +17,7 @@ import org.h2.command.Command; ...@@ -17,6 +17,7 @@ import org.h2.command.Command;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.command.dml.Query;
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;
...@@ -30,12 +31,14 @@ import org.h2.mvstore.db.TransactionStore.Change; ...@@ -30,12 +31,14 @@ import org.h2.mvstore.db.TransactionStore.Change;
import org.h2.mvstore.db.TransactionStore.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema; 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.SubQueryInfo;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.SmallLRUCache; import org.h2.util.SmallLRUCache;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -113,8 +116,11 @@ public class Session extends SessionWithState { ...@@ -113,8 +116,11 @@ public class Session extends SessionWithState {
private long modificationMetaID = -1; private long modificationMetaID = -1;
private SubQueryInfo subQueryInfo; private SubQueryInfo subQueryInfo;
private int parsingView; private int parsingView;
private int preparingQueryExpression;
private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache; private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
private HashMap<Object, ViewIndex> subQueryIndexCache; private HashMap<Object, ViewIndex> subQueryIndexCache;
private boolean joinBatchEnabled;
private boolean forceJoinOrder;
/** /**
* Temporary LOBs from result sets. Those are kept for some time. The * Temporary LOBs from result sets. Those are kept for some time. The
...@@ -148,12 +154,33 @@ public class Session extends SessionWithState { ...@@ -148,12 +154,33 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN; 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) { public Row createRow(Value[] data, int memory) {
return database.createRow(data, memory); return database.createRow(data, memory);
} }
public void setSubQueryInfo(SubQueryInfo subQueryInfo) { public void pushSubQueryInfo(int[] masks, TableFilter[] filters, int filter,
this.subQueryInfo = subQueryInfo; SortOrder sortOrder) {
subQueryInfo = new SubQueryInfo(subQueryInfo, masks, filters, filter, sortOrder);
}
public void popSubQueryInfo() {
subQueryInfo = subQueryInfo.getUpper();
} }
public SubQueryInfo getSubQueryInfo() { public SubQueryInfo getSubQueryInfo() {
...@@ -171,6 +198,24 @@ public class Session extends SessionWithState { ...@@ -171,6 +198,24 @@ public class Session extends SessionWithState {
return parsingView != 0; 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 @Override
public ArrayList<String> getClusterServers() { public ArrayList<String> getClusterServers() {
return new ArrayList<String>(); return new ArrayList<String>();
...@@ -492,6 +537,7 @@ public class Session extends SessionWithState { ...@@ -492,6 +537,7 @@ public class Session extends SessionWithState {
// we can't reuse sub-query indexes, so just drop the whole cache // we can't reuse sub-query indexes, so just drop the whole cache
subQueryIndexCache = null; subQueryIndexCache = null;
} }
command.prepareJoinBatch();
if (queryCache != null) { if (queryCache != null) {
if (command.isCacheable()) { if (command.isCacheable()) {
queryCache.put(sql, command); queryCache.put(sql, command);
......
...@@ -9,6 +9,7 @@ import org.h2.command.dml.Query; ...@@ -9,6 +9,7 @@ import org.h2.command.dml.Query;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -36,7 +37,7 @@ public class ConditionExists extends Condition { ...@@ -36,7 +37,7 @@ public class ConditionExists extends Condition {
@Override @Override
public Expression optimize(Session session) { public Expression optimize(Session session) {
query.prepare(); session.optimizeQueryExpression(query);
return this; return this;
} }
......
...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition; ...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -117,7 +118,7 @@ public class ConditionInSelect extends Condition { ...@@ -117,7 +118,7 @@ public class ConditionInSelect extends Condition {
public Expression optimize(Session session) { public Expression optimize(Session session) {
left = left.optimize(session); left = left.optimize(session);
query.setRandomAccessResult(true); query.setRandomAccessResult(true);
query.prepare(); session.optimizeQueryExpression(query);
if (query.getColumnCount() != 1) { if (query.getColumnCount() != 1) {
throw DbException.get(ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN); throw DbException.get(ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN);
} }
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
package org.h2.expression; package org.h2.expression;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.SubQueryInfo;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
...@@ -70,7 +70,7 @@ public class Subquery extends Expression { ...@@ -70,7 +70,7 @@ public class Subquery extends Expression {
@Override @Override
public Expression optimize(Session session) { public Expression optimize(Session session) {
query.prepare(); session.optimizeQueryExpression(query);
return this; return this;
} }
......
...@@ -5,11 +5,15 @@ ...@@ -5,11 +5,15 @@
*/ */
package org.h2.index; package org.h2.index;
import java.util.Arrays;
import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.result.Row; import org.h2.result.Row;
...@@ -21,6 +25,7 @@ import org.h2.table.IndexColumn; ...@@ -21,6 +25,7 @@ import org.h2.table.IndexColumn;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -148,65 +153,66 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -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 * b-tree range index. This is the estimated cost required to search one
* row, and then iterate over the given number of rows. * 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 rowCount the number of rows in the index
* @param filters all joined table filters * @param filters all joined table filters
* @param filter the current table filter index * @param filter the current table filter index
* @param sortOrder the sort order * @param sortOrder the sort order
* @return the estimated cost * @return the estimated cost
*/ */
protected long getCostRangeIndex(int[] masks, long rowCount, protected final long getCostRangeIndex(int[] masks, long rowCount, TableFilter[] filters, int filter,
TableFilter[] filters, int filter, SortOrder sortOrder) { SortOrder sortOrder, boolean isScanIndex) {
rowCount += Constants.COST_ROW_OFFSET; rowCount += Constants.COST_ROW_OFFSET;
long cost = rowCount;
long rows = rowCount;
int totalSelectivity = 0; int totalSelectivity = 0;
if (masks == null) { long rowsCost = rowCount;
return cost; if (masks != null) {
} for (int i = 0, len = columns.length; i < len; i++) {
for (int i = 0, len = columns.length; i < len; i++) { Column column = columns[i];
Column column = columns[i]; int index = column.getColumnId();
int index = column.getColumnId(); int mask = masks[index];
int mask = masks[index]; if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) { if (i == columns.length - 1 && getIndexType().isUnique()) {
if (i == columns.length - 1 && getIndexType().isUnique()) { rowsCost = 3;
cost = 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; 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, // If the ORDER BY clause matches the ordering of this index,
// it will be cheaper than another index, so adjust the cost accordingly // it will be cheaper than another index, so adjust the cost
// accordingly.
long sortingCost = 0;
if (sortOrder != null) { if (sortOrder != null) {
sortingCost = 100 + rowCount / 10;
}
if (sortOrder != null && !isScanIndex) {
boolean sortOrderMatches = true; boolean sortOrderMatches = true;
int coveringCount = 0; int coveringCount = 0;
int[] sortTypes = sortOrder.getSortTypes(); int[] sortTypes = sortOrder.getSortTypes();
TableFilter tableFilter = filters == null ? null : filters[filter]; TableFilter tableFilter = filters == null ? null : filters[filter];
for (int i = 0, len = sortTypes.length; i < len; i++) { for (int i = 0, len = sortTypes.length; i < len; i++) {
if (i >= indexColumns.length) { 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 // than it's columns, it's just that the coveringCount
// is lower than with an index that contains // is lower than with an index that contains
// more of the order by columns // more of the order by columns.
break; break;
} }
Column col = sortOrder.getColumn(i, tableFilter); Column col = sortOrder.getColumn(i, tableFilter);
...@@ -215,7 +221,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -215,7 +221,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
break; break;
} }
IndexColumn indexCol = indexColumns[i]; IndexColumn indexCol = indexColumns[i];
if (col != indexCol.column) { if (!col.equals(indexCol.column)) {
sortOrderMatches = false; sortOrderMatches = false;
break; break;
} }
...@@ -229,13 +235,51 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -229,13 +235,51 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
if (sortOrderMatches) { if (sortOrderMatches) {
// "coveringCount" makes sure that when we have two // "coveringCount" makes sure that when we have two
// or more covering indexes, we choose the one // or more covering indexes, we choose the one
// that covers more // that covers more.
cost -= coveringCount; 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 @Override
public int compareRows(SearchRow rowData, SearchRow compare) { public int compareRows(SearchRow rowData, SearchRow compare) {
if (rowData == compare) { if (rowData == compare) {
......
...@@ -15,7 +15,8 @@ import org.h2.result.SearchRow; ...@@ -15,7 +15,8 @@ import org.h2.result.SearchRow;
* method {@link #isBatchFull()}} will return {@code true} or there are no more * method {@link #isBatchFull()}} will return {@code true} or there are no more
* search rows to add. Then method {@link #find()} will be called to execute batched lookup. * search rows to add. Then method {@link #find()} will be called to execute batched lookup.
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple * Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups. * sequential batched lookups, moreover it can be reused for multiple queries for
* the same prepared statement.
* *
* @see Index#createLookupBatch(org.h2.table.TableFilter) * @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin * @author Sergi Vladykin
...@@ -26,9 +27,11 @@ public interface IndexLookupBatch { ...@@ -26,9 +27,11 @@ public interface IndexLookupBatch {
* *
* @param first the first row, or null for no limit * @param first the first row, or null for no limit
* @param last the last 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) * @see Index#find(TableFilter, SearchRow, SearchRow)
*/ */
void addSearchRows(SearchRow first, SearchRow last); boolean addSearchRows(SearchRow first, SearchRow last);
/** /**
* Check if this batch is full. * Check if this batch is full.
...@@ -47,4 +50,18 @@ public interface IndexLookupBatch { ...@@ -47,4 +50,18 @@ public interface IndexLookupBatch {
* @return List of future cursors for collected search rows. * @return List of future cursors for collected search rows.
*/ */
List<Future<Cursor>> find(); List<Future<Cursor>> find();
/**
* 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 { ...@@ -144,7 +144,7 @@ public class LinkedIndex extends BaseIndex {
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) {
return 100 + getCostRangeIndex(masks, rowCount + return 100 + getCostRangeIndex(masks, rowCount +
Constants.COST_ROW_OFFSET, filters, filter, sortOrder); Constants.COST_ROW_OFFSET, filters, filter, sortOrder, false);
} }
@Override @Override
......
...@@ -58,7 +58,7 @@ public class MetaIndex extends BaseIndex { ...@@ -58,7 +58,7 @@ public class MetaIndex extends BaseIndex {
return 10 * MetaTable.ROW_COUNT_APPROXIMATION; return 10 * MetaTable.ROW_COUNT_APPROXIMATION;
} }
return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION, return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION,
filters, filter, sortOrder); filters, filter, sortOrder, false);
} }
@Override @Override
......
...@@ -6,8 +6,6 @@ ...@@ -6,8 +6,6 @@
package org.h2.index; package org.h2.index;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
......
...@@ -220,7 +220,7 @@ public class PageBtreeIndex extends PageIndex { ...@@ -220,7 +220,7 @@ public class PageBtreeIndex extends PageIndex {
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) {
return 10 * getCostRangeIndex(masks, tableData.getRowCount(session), return 10 * getCostRangeIndex(masks, tableData.getRowCount(session),
filters, filter, sortOrder); filters, filter, sortOrder, false);
} }
@Override @Override
......
...@@ -99,7 +99,7 @@ public class PageDelegateIndex extends PageIndex { ...@@ -99,7 +99,7 @@ public class PageDelegateIndex extends PageIndex {
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) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session), return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session),
filters, filter, sortOrder); filters, filter, sortOrder, false);
} }
@Override @Override
......
...@@ -178,12 +178,6 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -178,12 +178,6 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
filter.getSession()); 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 * Compute spatial index cost
* @param masks Search mask * @param masks Search mask
...@@ -210,10 +204,10 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -210,10 +204,10 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@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) {
return getCostRangeIndex(masks, table.getRowCountApproximation(), return getCostRangeIndex(masks, table.getRowCountApproximation(), columns);
filters, filter, sortOrder);
} }
@Override @Override
public void remove(Session session) { public void remove(Session session) {
if (!treeMap.isClosed()) { if (!treeMap.isClosed()) {
......
...@@ -321,7 +321,7 @@ public class TreeIndex extends BaseIndex { ...@@ -321,7 +321,7 @@ public class TreeIndex extends BaseIndex {
public double getCost(Session session, int[] masks, TableFilter[] filters, int filter, public double getCost(Session session, int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder) { SortOrder sortOrder) {
return getCostRangeIndex(masks, tableData.getRowCountApproximation(), return getCostRangeIndex(masks, tableData.getRowCountApproximation(),
filters, filter, sortOrder); filters, filter, sortOrder, false);
} }
@Override @Override
......
...@@ -24,7 +24,7 @@ public class ViewCursor implements Cursor { ...@@ -24,7 +24,7 @@ public class ViewCursor implements Cursor {
private final SearchRow first, last; private final SearchRow first, last;
private Row current; private Row current;
ViewCursor(ViewIndex index, LocalResult result, SearchRow first, public ViewCursor(ViewIndex index, LocalResult result, SearchRow first,
SearchRow last) { SearchRow last) {
this.table = index.getTable(); this.table = index.getTable();
this.index = index; this.index = index;
......
...@@ -91,7 +91,7 @@ public class MVDelegateIndex extends BaseIndex implements MVIndex { ...@@ -91,7 +91,7 @@ public class MVDelegateIndex extends BaseIndex implements MVIndex {
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) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCountApproximation(), return 10 * getCostRangeIndex(masks, mainIndex.getRowCountApproximation(),
filters, filter, sortOrder); filters, filter, sortOrder, true);
} }
@Override @Override
......
...@@ -11,7 +11,6 @@ import java.util.Iterator; ...@@ -11,7 +11,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.index.BaseIndex; import org.h2.index.BaseIndex;
...@@ -219,8 +218,8 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -219,8 +218,8 @@ public class MVPrimaryIndex extends BaseIndex {
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) {
try { try {
long cost = 10 * (dataMap.sizeAsLongMax() + Constants.COST_ROW_OFFSET); return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
return cost; filters, filter, sortOrder, true);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e); throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
} }
......
...@@ -355,7 +355,7 @@ public class MVSecondaryIndex extends BaseIndex implements MVIndex { ...@@ -355,7 +355,7 @@ public class MVSecondaryIndex extends BaseIndex implements MVIndex {
TableFilter[] filters, int filter, SortOrder sortOrder) { TableFilter[] filters, int filter, SortOrder sortOrder) {
try { try {
return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(), return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
filters, filter, sortOrder); filters, filter, sortOrder, false);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e); throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
} }
......
...@@ -239,14 +239,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex { ...@@ -239,14 +239,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
@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) {
return getCostRangeIndex(masks, table.getRowCountApproximation(), return SpatialTreeIndex.getCostRangeIndex(masks, table.getRowCountApproximation(), columns);
filters, filter, sortOrder);
}
@Override
protected long getCostRangeIndex(int[] masks, long rowCount,
TableFilter[] filters, int filter, SortOrder sortOrder) {
return SpatialTreeIndex.getCostRangeIndex(masks, rowCount, columns);
} }
@Override @Override
......
...@@ -12,7 +12,6 @@ import java.sql.ResultSet; ...@@ -12,7 +12,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
...@@ -46,6 +45,7 @@ import org.h2.value.ValueStringFixed; ...@@ -46,6 +45,7 @@ import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase; import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid; import org.h2.value.ValueUuid;
/** /**
...@@ -133,20 +133,13 @@ public class ValueDataType implements DataType { ...@@ -133,20 +133,13 @@ public class ValueDataType implements DataType {
if (aNull || bNull) { if (aNull || bNull) {
return SortOrder.compareNull(aNull, sortType); return SortOrder.compareNull(aNull, sortType);
} }
int comp = compareTypeSafe(a, b); int comp = a.compareTypeSafe(b, compareMode);
if ((sortType & SortOrder.DESCENDING) != 0) { if ((sortType & SortOrder.DESCENDING) != 0) {
comp = -comp; comp = -comp;
} }
return comp; return comp;
} }
private int compareTypeSafe(Value a, Value b) {
if (a == b) {
return 0;
}
return a.compareTypeSafe(b, compareMode);
}
@Override @Override
public int getMemory(Object obj) { public int getMemory(Object obj) {
if (obj instanceof SpatialKey) { if (obj instanceof SpatialKey) {
...@@ -284,6 +277,12 @@ public class ValueDataType implements DataType { ...@@ -284,6 +277,12 @@ public class ValueDataType implements DataType {
putVarLong(nanos); putVarLong(nanos);
break; break;
} }
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
long dateTimeValue = ts.getUtcDateTimeNanos();
buff.put((byte) type).putVarLong(dateTimeValue);
break;
}
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
buff.put((byte) type). buff.put((byte) type).
...@@ -489,6 +488,10 @@ public class ValueDataType implements DataType { ...@@ -489,6 +488,10 @@ public class ValueDataType implements DataType {
long nanos = readVarLong(buff) * 1000000 + readVarLong(buff); long nanos = readVarLong(buff) * 1000000 + readVarLong(buff);
return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
} }
case Value.TIMESTAMP_UTC: {
long dateTimeValue = readVarLong(buff);
return ValueTimestampUtc.fromNanos(dateTimeValue);
}
case Value.BYTES: { case Value.BYTES: {
int len = readVarInt(buff); int len = readVarInt(buff);
byte[] b = DataUtils.newBytes(len); byte[] b = DataUtils.newBytes(len);
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
90084=Kann das letzte Feld nicht löschen {0} 90084=Kann das letzte Feld nicht löschen {0}
90085=Index {0} gehört zur Bedingung {1} 90085=Index {0} gehört zur Bedingung {1}
90086=Klasse {0} nicht gefunden 90086=Klasse {0} nicht gefunden
90087=Methode {0} nicht gefunden 90087=Method {0} with matching arguments not found
90088=Unbekannter Modus {0} 90088=Unbekannter Modus {0}
90089=Textvergleich-Modus kann nicht geändert werden wenn eine Daten-Tabelle existiert : {0} 90089=Textvergleich-Modus kann nicht geändert werden wenn eine Daten-Tabelle existiert : {0}
90090=Schema {0} kann nicht gelöscht werden 90090=Schema {0} kann nicht gelöscht werden
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
90084=Imposible eliminar la ultima columna {0} 90084=Imposible eliminar la ultima columna {0}
90085=Index {0} pertenece a un constraint {1} 90085=Index {0} pertenece a un constraint {1}
90086=Class {0} no encontrada 90086=Class {0} no encontrada
90087=Method {0} no encontrado 90087=Method {0} with matching arguments not found
90088=Modo desconocido {0} 90088=Modo desconocido {0}
90089=Collation no puede ser cambiado debido a que existe una tabla de datos: {0} 90089=Collation no puede ser cambiado debido a que existe una tabla de datos: {0}
90090=Schema {0} no puede ser eliminado 90090=Schema {0} no puede ser eliminado
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
90084=Nie można skasować ostatniej kolumny {0} 90084=Nie można skasować ostatniej kolumny {0}
90085=Indeks {0} należy do ograniczenia {1} 90085=Indeks {0} należy do ograniczenia {1}
90086=Klasa {0} nie istnieje 90086=Klasa {0} nie istnieje
90087=Metoda {0} nie istnieje 90087=Method {0} with matching arguments not found
90088=Nieznany stan {0} 90088=Nieznany stan {0}
90089=Metoda porównywania językowego nie może być zmieniona z powodu istnienia danych w tabeli {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 90090=Schemat {0} nie może zostać skasowany
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
90084=Não pode apagar a última coluna {0} 90084=Não pode apagar a última coluna {0}
90085=índice {0} pertence a uma restrição {1} 90085=índice {0} pertence a uma restrição {1}
90086=Classe {0} não foi encontrada 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 90088=Modo {0} desconhecido
90089=A coleção não pode ser alterada, porque existe uma tabela de dados: {0} 90089=A coleção não pode ser alterada, porque existe uma tabela de dados: {0}
90090=Esquema {0} não pode ser apagado 90090=Esquema {0} não pode ser apagado
......
...@@ -17,7 +17,6 @@ import java.sql.ResultSet; ...@@ -17,7 +17,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
...@@ -50,6 +49,7 @@ import org.h2.value.ValueStringFixed; ...@@ -50,6 +49,7 @@ import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase; import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid; import org.h2.value.ValueUuid;
/** /**
...@@ -538,6 +538,12 @@ public class Data { ...@@ -538,6 +538,12 @@ public class Data {
} }
break; break;
} }
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
writeByte((byte) type);
writeVarLong(ts.getUtcDateTimeNanos());
break;
}
case Value.GEOMETRY: case Value.GEOMETRY:
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
writeByte((byte) type); writeByte((byte) type);
...@@ -772,6 +778,9 @@ public class Data { ...@@ -772,6 +778,9 @@ public class Data {
DateTimeUtils.getTimeUTCWithoutDst(readVarLong()), DateTimeUtils.getTimeUTCWithoutDst(readVarLong()),
readVarInt()); readVarInt());
} }
case Value.TIMESTAMP_UTC: {
return ValueTimestampUtc.fromNanos(readVarLong());
}
case Value.BYTES: { case Value.BYTES: {
int len = readVarInt(); int len = readVarInt();
byte[] b = DataUtils.newBytes(len); byte[] b = DataUtils.newBytes(len);
...@@ -1020,6 +1029,10 @@ public class Data { ...@@ -1020,6 +1029,10 @@ public class Data {
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) +
getVarIntLen(ts.getNanos() % 1000000); getVarIntLen(ts.getNanos() % 1000000);
} }
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
return 1 + getVarLongLen(ts.getUtcDateTimeNanos());
}
case Value.GEOMETRY: case Value.GEOMETRY:
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
package org.h2.table; package org.h2.table;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -32,6 +31,7 @@ import org.h2.value.ValueNull; ...@@ -32,6 +31,7 @@ import org.h2.value.ValueNull;
import org.h2.value.ValueString; import org.h2.value.ValueString;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampUtc;
import org.h2.value.ValueUuid; import org.h2.value.ValueUuid;
/** /**
...@@ -294,6 +294,8 @@ public class Column { ...@@ -294,6 +294,8 @@ public class Column {
value = ValueInt.get(0).convertTo(type); value = ValueInt.get(0).convertTo(type);
} else if (dt.type == Value.TIMESTAMP) { } else if (dt.type == Value.TIMESTAMP) {
value = ValueTimestamp.fromMillis(session.getTransactionStart()); value = ValueTimestamp.fromMillis(session.getTransactionStart());
} else if (dt.type == Value.TIMESTAMP_UTC) {
value = ValueTimestampUtc.fromMillis(session.getTransactionStart());
} else if (dt.type == Value.TIME) { } else if (dt.type == Value.TIME) {
value = ValueTime.fromNanos(0); value = ValueTime.fromNanos(0);
} else if (dt.type == Value.DATE) { } else if (dt.type == Value.DATE) {
...@@ -399,7 +401,7 @@ public class Column { ...@@ -399,7 +401,7 @@ public class Column {
*/ */
public void prepareExpression(Session session) { public void prepareExpression(Session session) {
if (defaultExpression != null) { 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.mapColumns(computeTableFilter, 0);
defaultExpression = defaultExpression.optimize(session); defaultExpression = defaultExpression.optimize(session);
} }
......
差异被折叠。
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
package org.h2.table; package org.h2.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor; import org.h2.expression.ExpressionVisitor;
import org.h2.message.Trace;
import org.h2.table.TableFilter.TableFilterVisitor; import org.h2.table.TableFilter.TableFilterVisitor;
import org.h2.util.New; import org.h2.util.New;
...@@ -104,12 +106,23 @@ public class Plan { ...@@ -104,12 +106,23 @@ public class Plan {
* @return the cost * @return the cost
*/ */
public double calculateCost(Session session) { 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; double cost = 1;
boolean invalidPlan = false; boolean invalidPlan = false;
for (int i = 0; i < allFilters.length; i++) { for (int i = 0; i < allFilters.length; i++) {
TableFilter tableFilter = allFilters[i]; TableFilter tableFilter = allFilters[i];
if (t.isDebugEnabled()) {
t.debug("Plan : for table filter {0}", tableFilter);
}
PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i); PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i);
planItems.put(tableFilter, item); 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; cost += cost * item.cost;
setEvaluatable(tableFilter, true); setEvaluatable(tableFilter, true);
Expression on = tableFilter.getJoinCondition(); Expression on = tableFilter.getJoinCondition();
...@@ -123,6 +136,9 @@ public class Plan { ...@@ -123,6 +136,9 @@ public class Plan {
if (invalidPlan) { if (invalidPlan) {
cost = Double.POSITIVE_INFINITY; cost = Double.POSITIVE_INFINITY;
} }
if (t.isDebugEnabled()) {
session.getTrace().debug("Plan : plan cost {0}", cost);
}
for (TableFilter f : allFilters) { for (TableFilter f : allFilters) {
setEvaluatable(f, false); setEvaluatable(f, false);
} }
......
...@@ -19,7 +19,6 @@ public class SubQueryInfo { ...@@ -19,7 +19,6 @@ public class SubQueryInfo {
private TableFilter[] filters; private TableFilter[] filters;
private int filter; private int filter;
private SortOrder sortOrder; private SortOrder sortOrder;
private boolean preliminary;
private SubQueryInfo upper; private SubQueryInfo upper;
/** /**
...@@ -28,17 +27,14 @@ public class SubQueryInfo { ...@@ -28,17 +27,14 @@ public class SubQueryInfo {
* @param filters table filters * @param filters table filters
* @param filter current filter * @param filter current filter
* @param sortOrder sort order * @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, public SubQueryInfo(SubQueryInfo upper, int[] masks, TableFilter[] filters, int filter,
SortOrder sortOrder, boolean preliminary) { SortOrder sortOrder) {
this.upper = upper; this.upper = upper;
this.masks = masks; this.masks = masks;
this.filters = filters; this.filters = filters;
this.filter = filter; this.filter = filter;
this.sortOrder = sortOrder; this.sortOrder = sortOrder;
this.preliminary = preliminary;
} }
public SubQueryInfo getUpper() { public SubQueryInfo getUpper() {
...@@ -60,8 +56,4 @@ public class SubQueryInfo { ...@@ -60,8 +56,4 @@ public class SubQueryInfo {
public SortOrder getSortOrder() { public SortOrder getSortOrder() {
return sortOrder; return sortOrder;
} }
public boolean isPreliminary() {
return preliminary;
}
} }
...@@ -127,6 +127,10 @@ public abstract class Table extends SchemaObjectBase { ...@@ -127,6 +127,10 @@ public abstract class Table extends SchemaObjectBase {
} }
} }
public boolean isView() {
return false;
}
/** /**
* Lock the table for the given session. * Lock the table for the given session.
* This method waits until the lock is granted. * This method waits until the lock is granted.
...@@ -693,11 +697,20 @@ public abstract class Table extends SchemaObjectBase { ...@@ -693,11 +697,20 @@ public abstract class Table extends SchemaObjectBase {
PlanItem item = new PlanItem(); PlanItem item = new PlanItem();
item.setIndex(getScanIndex(session)); item.setIndex(getScanIndex(session));
item.cost = item.getIndex().getCost(session, null, filters, filter, null); 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(); ArrayList<Index> indexes = getIndexes();
if (indexes != null && masks != null) { if (indexes != null && masks != null) {
for (int i = 1, size = indexes.size(); i < size; i++) { for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i); Index index = indexes.get(i);
double cost = index.getCost(session, masks, filters, filter, sortOrder); 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) { if (cost < item.cost) {
item.cost = cost; item.cost = cost;
item.setIndex(index); item.setIndex(index);
......
...@@ -218,6 +218,11 @@ public class TableView extends Table { ...@@ -218,6 +218,11 @@ public class TableView extends Table {
} }
} }
@Override
public boolean isView() {
return true;
}
/** /**
* Check if this view is currently invalid. * Check if this view is currently invalid.
* *
...@@ -230,15 +235,15 @@ public class TableView extends Table { ...@@ -230,15 +235,15 @@ public class TableView extends Table {
@Override @Override
public PlanItem getBestPlanItem(Session session, int[] masks, public PlanItem getBestPlanItem(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) { 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); final CacheKey cacheKey = new CacheKey(masks, this);
Map<Object, ViewIndex> indexCache = session.getViewIndexCache(topQuery != null); Map<Object, ViewIndex> indexCache = session.getViewIndexCache(topQuery != null);
ViewIndex i = indexCache.get(cacheKey); ViewIndex i = indexCache.get(cacheKey);
if (i == null) { if (i == null || i.isExpired()) {
i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder); i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder);
indexCache.put(cacheKey, i); indexCache.put(cacheKey, i);
} }
PlanItem item = new PlanItem();
item.cost = i.getCost(session, masks, filters, filter, sortOrder);
item.setIndex(i); item.setIndex(i);
return item; 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; ...@@ -22,7 +22,6 @@ import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SessionInterface; import org.h2.engine.SessionInterface;
...@@ -315,6 +314,13 @@ public class DataType { ...@@ -315,6 +314,13 @@ public class DataType {
// 24 for ValueTimestamp, 32 for java.sql.Timestamp // 24 for ValueTimestamp, 32 for java.sql.Timestamp
56 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", add(Value.BYTES, Types.VARBINARY, "Bytes",
createString(false), createString(false),
new String[]{"VARBINARY"}, new String[]{"VARBINARY"},
...@@ -539,6 +545,12 @@ public class DataType { ...@@ -539,6 +545,12 @@ public class DataType {
ValueTimestamp.get(value); ValueTimestamp.get(value);
break; 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: { case Value.DECIMAL: {
BigDecimal value = rs.getBigDecimal(columnIndex); BigDecimal value = rs.getBigDecimal(columnIndex);
v = value == null ? (Value) ValueNull.INSTANCE : v = value == null ? (Value) ValueNull.INSTANCE :
...@@ -711,6 +723,7 @@ public class DataType { ...@@ -711,6 +723,7 @@ public class DataType {
// "java.sql.Date"; // "java.sql.Date";
return Date.class.getName(); return Date.class.getName();
case Value.TIMESTAMP: case Value.TIMESTAMP:
case Value.TIMESTAMP_UTC:
// "java.sql.Timestamp"; // "java.sql.Timestamp";
return Timestamp.class.getName(); return Timestamp.class.getName();
case Value.BYTES: case Value.BYTES:
......
...@@ -18,7 +18,6 @@ import java.sql.ResultSet; ...@@ -18,7 +18,6 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SessionInterface; import org.h2.engine.SessionInterface;
...@@ -388,6 +387,11 @@ public class Transfer { ...@@ -388,6 +387,11 @@ public class Transfer {
} }
break; break;
} }
case Value.TIMESTAMP_UTC: {
ValueTimestampUtc ts = (ValueTimestampUtc) v;
writeLong(ts.getUtcDateTimeNanos());
break;
}
case Value.DECIMAL: case Value.DECIMAL:
writeString(v.getString()); writeString(v.getString());
break; break;
...@@ -574,6 +578,9 @@ public class Transfer { ...@@ -574,6 +578,9 @@ public class Transfer {
return ValueTimestamp.fromMillisNanos(readLong(), return ValueTimestamp.fromMillisNanos(readLong(),
readInt() % 1000000); readInt() % 1000000);
} }
case Value.TIMESTAMP_UTC: {
return ValueTimestampUtc.fromNanos(readLong());
}
case Value.DECIMAL: case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(readString())); return ValueDecimal.get(new BigDecimal(readString()));
case Value.DOUBLE: case Value.DOUBLE:
......
...@@ -18,7 +18,6 @@ import java.sql.SQLException; ...@@ -18,7 +18,6 @@ import java.sql.SQLException;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
...@@ -159,11 +158,15 @@ public abstract class Value { ...@@ -159,11 +158,15 @@ public abstract class Value {
* The value type for string values with a fixed size. * The value type for string values with a fixed size.
*/ */
public static final int GEOMETRY = 22; 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. * 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 = private static SoftReference<Value[]> softCache =
new SoftReference<Value[]>(null); new SoftReference<Value[]>(null);
...@@ -299,6 +302,8 @@ public abstract class Value { ...@@ -299,6 +302,8 @@ public abstract class Value {
return 31; return 31;
case TIMESTAMP: case TIMESTAMP:
return 32; return 32;
case TIMESTAMP_UTC:
return 33;
case BYTES: case BYTES:
return 40; return 40;
case BLOB: case BLOB:
...@@ -542,6 +547,7 @@ public abstract class Value { ...@@ -542,6 +547,7 @@ public abstract class Value {
case TIME: case TIME:
case DATE: case DATE:
case TIMESTAMP: case TIMESTAMP:
case TIMESTAMP_UTC:
case BYTES: case BYTES:
case JAVA_OBJECT: case JAVA_OBJECT:
case UUID: case UUID:
...@@ -559,6 +565,7 @@ public abstract class Value { ...@@ -559,6 +565,7 @@ public abstract class Value {
case INT: case INT:
return ValueByte.get(convertToByte(getInt())); return ValueByte.get(convertToByte(getInt()));
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueByte.get(convertToByte(getLong())); return ValueByte.get(convertToByte(getLong()));
case DECIMAL: case DECIMAL:
return ValueByte.get(convertToByte(convertToLong(getBigDecimal()))); return ValueByte.get(convertToByte(convertToLong(getBigDecimal())));
...@@ -580,6 +587,7 @@ public abstract class Value { ...@@ -580,6 +587,7 @@ public abstract class Value {
case INT: case INT:
return ValueShort.get(convertToShort(getInt())); return ValueShort.get(convertToShort(getInt()));
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueShort.get(convertToShort(getLong())); return ValueShort.get(convertToShort(getLong()));
case DECIMAL: case DECIMAL:
return ValueShort.get(convertToShort(convertToLong(getBigDecimal()))); return ValueShort.get(convertToShort(convertToLong(getBigDecimal())));
...@@ -601,6 +609,7 @@ public abstract class Value { ...@@ -601,6 +609,7 @@ public abstract class Value {
case SHORT: case SHORT:
return ValueInt.get(getShort()); return ValueInt.get(getShort());
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueInt.get(convertToInt(getLong())); return ValueInt.get(convertToInt(getLong()));
case DECIMAL: case DECIMAL:
return ValueInt.get(convertToInt(convertToLong(getBigDecimal()))); return ValueInt.get(convertToInt(convertToLong(getBigDecimal())));
...@@ -637,6 +646,8 @@ public abstract class Value { ...@@ -637,6 +646,8 @@ public abstract class Value {
} }
return ValueLong.get(Long.parseLong(getString(), 16)); return ValueLong.get(Long.parseLong(getString(), 16));
} }
case TIMESTAMP_UTC:
return ValueLong.get(getLong());
} }
break; break;
} }
...@@ -652,6 +663,7 @@ public abstract class Value { ...@@ -652,6 +663,7 @@ public abstract class Value {
case INT: case INT:
return ValueDecimal.get(BigDecimal.valueOf(getInt())); return ValueDecimal.get(BigDecimal.valueOf(getInt()));
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueDecimal.get(BigDecimal.valueOf(getLong())); return ValueDecimal.get(BigDecimal.valueOf(getLong()));
case DOUBLE: { case DOUBLE: {
double d = getDouble(); double d = getDouble();
...@@ -684,6 +696,7 @@ public abstract class Value { ...@@ -684,6 +696,7 @@ public abstract class Value {
case INT: case INT:
return ValueDouble.get(getInt()); return ValueDouble.get(getInt());
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueDouble.get(getLong()); return ValueDouble.get(getLong());
case DECIMAL: case DECIMAL:
return ValueDouble.get(getBigDecimal().doubleValue()); return ValueDouble.get(getBigDecimal().doubleValue());
...@@ -703,6 +716,7 @@ public abstract class Value { ...@@ -703,6 +716,7 @@ public abstract class Value {
case INT: case INT:
return ValueFloat.get(getInt()); return ValueFloat.get(getInt());
case LONG: case LONG:
case TIMESTAMP_UTC:
return ValueFloat.get(getLong()); return ValueFloat.get(getLong());
case DECIMAL: case DECIMAL:
return ValueFloat.get(getBigDecimal().floatValue()); return ValueFloat.get(getBigDecimal().floatValue());
...@@ -721,6 +735,9 @@ public abstract class Value { ...@@ -721,6 +735,9 @@ public abstract class Value {
case TIMESTAMP: case TIMESTAMP:
return ValueDate.fromDateValue( return ValueDate.fromDateValue(
((ValueTimestamp) this).getDateValue()); ((ValueTimestamp) this).getDateValue());
case TIMESTAMP_UTC:
return ValueDate.fromMillis(
((ValueTimestampUtc) this).getUtcDateTimeMillis());
} }
break; break;
} }
...@@ -733,6 +750,9 @@ public abstract class Value { ...@@ -733,6 +750,9 @@ public abstract class Value {
case TIMESTAMP: case TIMESTAMP:
return ValueTime.fromNanos( return ValueTime.fromNanos(
((ValueTimestamp) this).getTimeNanos()); ((ValueTimestamp) this).getTimeNanos());
case TIMESTAMP_UTC:
return ValueTime.fromMillis(
((ValueTimestampUtc) this).getUtcDateTimeMillis());
} }
break; break;
} }
...@@ -744,6 +764,35 @@ public abstract class Value { ...@@ -744,6 +764,35 @@ public abstract class Value {
case DATE: case DATE:
return ValueTimestamp.fromDateValueAndNanos( return ValueTimestamp.fromDateValueAndNanos(
((ValueDate) this).getDateValue(), 0); ((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; break;
} }
...@@ -860,6 +909,8 @@ public abstract class Value { ...@@ -860,6 +909,8 @@ public abstract class Value {
return ValueDate.parse(s.trim()); return ValueDate.parse(s.trim());
case TIMESTAMP: case TIMESTAMP:
return ValueTimestamp.parse(s.trim()); return ValueTimestamp.parse(s.trim());
case TIMESTAMP_UTC:
return ValueTimestampUtc.parse(s.trim());
case BYTES: case BYTES:
return ValueBytes.getNoCopy( return ValueBytes.getNoCopy(
StringUtils.convertHexToBytes(s.trim())); 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 { ...@@ -80,10 +80,10 @@ public class TestSelectCountNonNullColumn extends TestBase {
if (expect >= 0) { if (expect >= 0) {
assertEquals(expect, rs.getLong(1)); assertEquals(expect, rs.getLong(1));
} else { } else {
// System.out.println(rs.getString(1)); assertEquals("SELECT\n"
assertEquals("SELECT\n" + " COUNT(KEY)\n" + " COUNT(KEY)\n"
+ "FROM PUBLIC.SIMPLE\n" + "FROM PUBLIC.SIMPLE\n"
+ " /* PUBLIC.SIMPLE.tableScan */\n" + " /* PUBLIC.PRIMARY_KEY_9 */\n"
+ "/* direct lookup */", rs.getString(1)); + "/* direct lookup */", rs.getString(1));
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论