提交 eed6fa58 authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 c12cf0a7
...@@ -109,6 +109,7 @@ import org.h2.expression.ValueExpression; ...@@ -109,6 +109,7 @@ import org.h2.expression.ValueExpression;
import org.h2.expression.Wildcard; import org.h2.expression.Wildcard;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.message.Message; import org.h2.message.Message;
import org.h2.result.SortOrder;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.schema.Sequence; import org.h2.schema.Sequence;
import org.h2.schema.TriggerObject; import org.h2.schema.TriggerObject;
...@@ -137,6 +138,9 @@ import org.h2.value.ValueString; ...@@ -137,6 +138,9 @@ 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;
/**
* The parser is used to convert a SQL statement string to an command object.
*/
public class Parser { public class Parser {
// used during the tokenizer phase // used during the tokenizer phase
...@@ -678,9 +682,18 @@ public class Parser { ...@@ -678,9 +682,18 @@ public class Parser {
columns.add(column); columns.add(column);
if (readIf("ASC")) { if (readIf("ASC")) {
// ignore // ignore
} else if (readIf("DESC")) {
column.sortType = SortOrder.DESCENDING;
}
if (readIf("NULLS")) {
if (readIf("FIRST")) {
column.sortType |= SortOrder.NULLS_FIRST;
} else { } else {
readIf("DESC"); read("LAST");
column.descending = true; column.sortType |= SortOrder.NULLS_LAST;
}
} else {
} }
} while (readIf(",")); } while (readIf(","));
read(")"); read(")");
......
...@@ -15,6 +15,9 @@ import org.h2.table.PlanItem; ...@@ -15,6 +15,9 @@ import org.h2.table.PlanItem;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.Permutations; import org.h2.util.Permutations;
/**
* The optimizer is responsible to find the best execution plan for a given query.
*/
public class Optimizer { public class Optimizer {
private static final int MAX_BRUTE_FORCE_FILTERS = 7; private static final int MAX_BRUTE_FORCE_FILTERS = 7;
......
...@@ -22,6 +22,7 @@ import org.h2.result.LocalResult; ...@@ -22,6 +22,7 @@ import org.h2.result.LocalResult;
import org.h2.result.SortOrder; 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.Table; import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
...@@ -32,6 +33,8 @@ import org.h2.value.ValueArray; ...@@ -32,6 +33,8 @@ import org.h2.value.ValueArray;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* This class represents a simple SELECT statement.
*
* visibleColumnCount <= distinctColumnCount <= expressionCount * visibleColumnCount <= distinctColumnCount <= expressionCount
* Sortable count could include ORDER BY expressions that are not in the select list * Sortable count could include ORDER BY expressions that are not in the select list
* Expression count could include GROUP BY expressions * Expression count could include GROUP BY expressions
...@@ -60,6 +63,7 @@ public class Select extends Query { ...@@ -60,6 +63,7 @@ public class Select extends Query {
private double cost; private double cost;
private boolean isQuickQuery; private boolean isQuickQuery;
private boolean isPrepared, checkInit; private boolean isPrepared, checkInit;
private boolean sortUsingIndex;
private SortOrder sort; private SortOrder sort;
public Select(Session session) { public Select(Session session) {
...@@ -190,16 +194,18 @@ public class Select extends Query { ...@@ -190,16 +194,18 @@ public class Select extends Query {
} }
} }
/**
* Get the index that matches the ORDER BY list, if one exists.
* This is to avoid running a separate ORDER BY if an index can be used.
* This is specially important for large result sets, if only the first few rows are important
* (LIMIT is used)
*
* @return the index if one is found
*/
private Index getSortIndex() throws SQLException { private Index getSortIndex() throws SQLException {
if (sort == null) { if (sort == null) {
return null; return null;
} }
int[] sortTypes = sort.getSortTypes();
for (int i = 0; i < sortTypes.length; i++) {
if ((sortTypes[i] & (SortOrder.DESCENDING | SortOrder.NULLS_LAST)) != 0) {
return null;
}
}
int[] indexes = sort.getIndexes(); int[] indexes = sort.getIndexes();
ObjectArray sortColumns = new ObjectArray(); ObjectArray sortColumns = new ObjectArray();
for (int i = 0; i < indexes.length; i++) { for (int i = 0; i < indexes.length; i++) {
...@@ -223,6 +229,7 @@ public class Select extends Query { ...@@ -223,6 +229,7 @@ public class Select extends Query {
} }
Column[] sortCols = new Column[sortColumns.size()]; Column[] sortCols = new Column[sortColumns.size()];
sortColumns.toArray(sortCols); sortColumns.toArray(sortCols);
int[] sortTypes = sort.getSortTypes();
if (sortCols.length == 0) { if (sortCols.length == 0) {
// sort just on constants - can use scan index // sort just on constants - can use scan index
return topTableFilter.getTable().getScanIndex(session); return topTableFilter.getTable().getScanIndex(session);
...@@ -237,15 +244,22 @@ public class Select extends Query { ...@@ -237,15 +244,22 @@ public class Select extends Query {
if (index.getIndexType().isHash()) { if (index.getIndexType().isHash()) {
continue; continue;
} }
Column[] indexCols = index.getColumns(); IndexColumn[] indexCols = index.getIndexColumns();
if (indexCols.length < sortCols.length) { if (indexCols.length < sortCols.length) {
continue; continue;
} }
boolean ok = true; boolean ok = true;
for (int j = 0; j < sortCols.length; j++) { for (int j = 0; j < sortCols.length; j++) {
// the index and the sort order must start with the exact same // the index and the sort order must start
// columns // with the exact same columns
if (indexCols[j] != sortCols[j]) { IndexColumn idxCol = indexCols[j];
Column sortCol = sortCols[j];
if (idxCol.column != sortCol) {
ok = false;
break;
}
if (idxCol.sortType != sortTypes[j]) {
// TODO NULL FIRST for ascending and NULLS LAST for descending would actually match the default
ok = false; ok = false;
break; break;
} }
...@@ -274,7 +288,7 @@ public class Select extends Query { ...@@ -274,7 +288,7 @@ public class Select extends Query {
} }
result.addRow(row); result.addRow(row);
rowNumber++; rowNumber++;
if (sort == null && limitRows != 0 && result.getRowCount() >= limitRows) { if ((sort == null || sortUsingIndex) && limitRows != 0 && result.getRowCount() >= limitRows) {
break; break;
} }
if (sampleSize > 0 && rowNumber >= sampleSize) { if (sampleSize > 0 && rowNumber >= sampleSize) {
...@@ -311,7 +325,9 @@ public class Select extends Query { ...@@ -311,7 +325,9 @@ public class Select extends Query {
} }
int columnCount = expressions.size(); int columnCount = expressions.size();
LocalResult result = new LocalResult(session, expressions, visibleColumnCount); LocalResult result = new LocalResult(session, expressions, visibleColumnCount);
if (!sortUsingIndex) {
result.setSortOrder(sort); result.setSortOrder(sort);
}
if (distinct) { if (distinct) {
result.setDistinct(); result.setDistinct();
} }
...@@ -489,7 +505,7 @@ public class Select extends Query { ...@@ -489,7 +505,7 @@ public class Select extends Query {
Index current = topTableFilter.getIndex(); Index current = topTableFilter.getIndex();
if (index != null && (current.getIndexType().isScan() || current == index)) { if (index != null && (current.getIndexType().isScan() || current == index)) {
topTableFilter.setIndex(index); topTableFilter.setIndex(index);
sort = null; sortUsingIndex = true;
} }
} }
} }
......
...@@ -64,6 +64,9 @@ import org.h2.constant.SysProperties; ...@@ -64,6 +64,9 @@ import org.h2.constant.SysProperties;
* *
* @author Thomas * @author Thomas
*/ */
/**
* Constants are fixed values that are used in the whole database code.
*/
public class Constants { public class Constants {
public static final int BUILD_ID = 60; public static final int BUILD_ID = 60;
...@@ -82,9 +85,6 @@ public class Constants { ...@@ -82,9 +85,6 @@ public class Constants {
return VERSION_MAJOR + "." + VERSION_MINOR + "." + BUILD_ID + " (" + BUILD + ")"; return VERSION_MAJOR + "." + VERSION_MINOR + "." + BUILD_ID + " (" + BUILD + ")";
} }
public static final int NULL_SORT_LOW = 1, NULL_SORT_HIGH = 2;
public static final int NULL_SORT_START = 3, NULL_SORT_END = 4;
public static final int NULL_SORT_DEFAULT = NULL_SORT_LOW;
public static final int DEFAULT_SERVER_PORT = 9092; // this is also in the docs public static final int DEFAULT_SERVER_PORT = 9092; // this is also in the docs
public static final String START_URL = "jdbc:h2:"; public static final String START_URL = "jdbc:h2:";
public static final String URL_FORMAT = START_URL + "{ {.|mem:}[name] | [file:]fileName | {tcp|ssl}:[//]server[:port][,server2[:port]]/name }[;key=value...]"; public static final String URL_FORMAT = START_URL + "{ {.|mem:}[name] | [file:]fileName | {tcp|ssl}:[//]server[:port][,server2[:port]]/name }[;key=value...]";
......
...@@ -59,10 +59,11 @@ import org.h2.value.Value; ...@@ -59,10 +59,11 @@ import org.h2.value.Value;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
/** /**
* There is one database object per open database.
*
* @author Thomas * @author Thomas
* @since 2004-04-15 22:49 * @since 2004-04-15 22:49
*/ */
/* /*
* MetaData format: int id int headPos (for indexes) int objectType String sql * MetaData format: int id int headPos (for indexes) int objectType String sql
*/ */
......
...@@ -6,6 +6,9 @@ package org.h2.engine; ...@@ -6,6 +6,9 @@ package org.h2.engine;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/**
* This class is reponsible to close a database if the application did not close a connection.
*/
public class DatabaseCloser extends Thread { public class DatabaseCloser extends Thread {
private final boolean shutdownHook; private final boolean shutdownHook;
...@@ -52,13 +55,14 @@ public class DatabaseCloser extends Thread { ...@@ -52,13 +55,14 @@ public class DatabaseCloser extends Thread {
return; return;
} }
} }
Database database = null;
synchronized (this) { synchronized (this) {
if (databaseRef != null) { if (databaseRef != null) {
Database database = (Database) databaseRef.get(); database = (Database) databaseRef.get();
if (database != null) {
database.close(shutdownHook);
} }
} }
if (database != null) {
database.close(shutdownHook);
} }
} }
......
...@@ -161,6 +161,10 @@ public class Aggregate extends Expression { ...@@ -161,6 +161,10 @@ public class Aggregate extends Expression {
case MAX: case MAX:
boolean first = type == MIN; boolean first = type == MIN;
Index index = getColumnIndex(first); Index index = getColumnIndex(first);
int sortType = index.getIndexColumns()[0].sortType;
if ((sortType & SortOrder.DESCENDING) != 0) {
first = !first;
}
SearchRow row = index.findFirstOrLast(session, first); SearchRow row = index.findFirstOrLast(session, first);
Value v; Value v;
if (row == null) { if (row == null) {
......
...@@ -19,7 +19,7 @@ import org.h2.value.ValueBoolean; ...@@ -19,7 +19,7 @@ import org.h2.value.ValueBoolean;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* @author Thomas * Example comparison expressions are ID=1, NAME=NAME, NAME IS NULL.
*/ */
public class Comparison extends Condition { public class Comparison extends Condition {
......
...@@ -15,7 +15,7 @@ import org.h2.value.Value; ...@@ -15,7 +15,7 @@ import org.h2.value.Value;
/** /**
* @author Thomas * An expression is a operation, a value, or a function in a query.
*/ */
public abstract class Expression { public abstract class Expression {
......
...@@ -22,9 +22,6 @@ import org.h2.table.TableFilter; ...@@ -22,9 +22,6 @@ import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
/**
* @author Thomas
*/
public class ExpressionColumn extends Expression { public class ExpressionColumn extends Expression {
private Database database; private Database database;
private String schemaName; private String schemaName;
......
...@@ -14,6 +14,7 @@ import org.h2.message.Message; ...@@ -14,6 +14,7 @@ import org.h2.message.Message;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.schema.SchemaObjectBase; import org.h2.schema.SchemaObjectBase;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
...@@ -23,7 +24,7 @@ import org.h2.value.Value; ...@@ -23,7 +24,7 @@ import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* @author Thomas * Most index implementations extend the base index.
*/ */
public abstract class BaseIndex extends SchemaObjectBase implements Index { public abstract class BaseIndex extends SchemaObjectBase implements Index {
...@@ -90,7 +91,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -90,7 +91,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
public abstract void truncate(Session session) throws SQLException; public abstract void truncate(Session session) throws SQLException;
public abstract boolean canGetFirstOrLast(boolean first); public abstract boolean canGetFirstOrLast();
public abstract SearchRow findFirstOrLast(Session session, boolean first) throws SQLException; public abstract SearchRow findFirstOrLast(Session session, boolean first) throws SQLException;
...@@ -144,14 +145,14 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -144,14 +145,14 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
} }
public int compareRows(SearchRow rowData, SearchRow compare) throws SQLException { public int compareRows(SearchRow rowData, SearchRow compare) throws SQLException {
for (int i = 0; i < columns.length; i++) { for (int i = 0; i < indexColumns.length; i++) {
int index = columnIds[i]; int index = columnIds[i];
Value v = compare.getValue(index); Value v = compare.getValue(index);
if (v == null) { if (v == null) {
// can't compare further // can't compare further
return 0; return 0;
} }
int c = compareValues(rowData.getValue(index), v); int c = compareValues(rowData.getValue(index), v, indexColumns[i].sortType);
if (c != 0) { if (c != 0) {
return c; return c;
} }
...@@ -179,17 +180,19 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -179,17 +180,19 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
return k1 > k2 ? 1 : -1; return k1 > k2 ? 1 : -1;
} }
private int compareValues(Value v1, Value v2) throws SQLException { private int compareValues(Value a, Value b, int sortType) throws SQLException {
if (v1 == null) { boolean aNull = a == null, bNull = b == null;
if (v2 == null) { if (aNull || bNull) {
if (aNull == bNull) {
return 0; return 0;
} }
return 1; return SortOrder.compareNull(aNull, bNull, sortType);
} }
if (v2 == null) { int comp = database.compareTypeSave(a, b);
return -1; if ((sortType & SortOrder.DESCENDING) != 0) {
comp = -comp;
} }
return database.compareTypeSave(v1, v2); return comp;
} }
public int getColumnIndex(Column col) { public int getColumnIndex(Column col) {
......
...@@ -25,7 +25,7 @@ import org.h2.value.Value; ...@@ -25,7 +25,7 @@ import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* @author Thomas * This is the most common type of index, a b tree index.
*/ */
public class BtreeIndex extends BaseIndex implements RecordReader { public class BtreeIndex extends BaseIndex implements RecordReader {
...@@ -284,7 +284,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader { ...@@ -284,7 +284,7 @@ public class BtreeIndex extends BaseIndex implements RecordReader {
return lastChange; return lastChange;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return true; return true;
} }
......
...@@ -65,7 +65,7 @@ public class FunctionIndex extends BaseIndex { ...@@ -65,7 +65,7 @@ public class FunctionIndex extends BaseIndex {
throw Message.getUnsupportedException(); throw Message.getUnsupportedException();
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -142,7 +142,7 @@ public class HashIndex extends BaseIndex { ...@@ -142,7 +142,7 @@ public class HashIndex extends BaseIndex {
return true; return true;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -36,7 +36,7 @@ public interface Index extends SchemaObject { ...@@ -36,7 +36,7 @@ public interface Index extends SchemaObject {
void truncate(Session session) throws SQLException; void truncate(Session session) throws SQLException;
boolean canGetFirstOrLast(boolean first); boolean canGetFirstOrLast();
SearchRow findFirstOrLast(Session session, boolean first) throws SQLException; SearchRow findFirstOrLast(Session session, boolean first) throws SQLException;
......
...@@ -15,9 +15,9 @@ import org.h2.message.Message; ...@@ -15,9 +15,9 @@ import org.h2.message.Message;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.value.Value; import org.h2.value.Value;
/** /**
* @author Thomas * A index condition object is made for each condition that can potentially use an index.
* This class does not extend expression, but in general there is one expression that maps to each index condition.
*/ */
public class IndexCondition { public class IndexCondition {
public static final int EQUALITY = 1, START = 2, END = 4, RANGE = START | END, ALWAYS_FALSE = 8; public static final int EQUALITY = 1, START = 2, END = 4, RANGE = START | END, ALWAYS_FALSE = 8;
......
...@@ -532,7 +532,7 @@ public class LinearHashIndex extends BaseIndex implements RecordReader { ...@@ -532,7 +532,7 @@ public class LinearHashIndex extends BaseIndex implements RecordReader {
return bucketSize; return bucketSize;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -157,7 +157,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -157,7 +157,7 @@ public class LinkedIndex extends BaseIndex {
return false; return false;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -82,7 +82,7 @@ public class MetaIndex extends BaseIndex { ...@@ -82,7 +82,7 @@ public class MetaIndex extends BaseIndex {
return null; return null;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -120,9 +120,6 @@ public class MultiVersionCursor implements Cursor { ...@@ -120,9 +120,6 @@ public class MultiVersionCursor implements Cursor {
return true; return true;
} }
} }
int test;
this.index.debug("error", session, deltaRow);
System.exit(1);
throw Message.getInternalError(); throw Message.getInternalError();
} }
int compare = index.compareRows(deltaRow, baseRow); int compare = index.compareRows(deltaRow, baseRow);
......
...@@ -33,8 +33,6 @@ public class MultiVersionIndex implements Index { ...@@ -33,8 +33,6 @@ public class MultiVersionIndex implements Index {
} }
public void add(Session session, Row row) throws SQLException { public void add(Session session, Row row) throws SQLException {
int test;
debug("add", session, row);
synchronized (sync) { synchronized (sync) {
base.add(session, row); base.add(session, row);
if (removeIfExists(session, row)) { if (removeIfExists(session, row)) {
...@@ -53,8 +51,6 @@ debug("add", session, row); ...@@ -53,8 +51,6 @@ debug("add", session, row);
} }
public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException { public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
int test;
debug("find", session, first);
synchronized (sync) { synchronized (sync) {
Cursor baseCursor = base.find(session, first, last); Cursor baseCursor = base.find(session, first, last);
Cursor deltaCursor = delta.find(session, first, last); Cursor deltaCursor = delta.find(session, first, last);
...@@ -62,7 +58,7 @@ debug("find", session, first); ...@@ -62,7 +58,7 @@ debug("find", session, first);
} }
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
// TODO in many cases possible, but more complicated // TODO in many cases possible, but more complicated
return false; return false;
} }
...@@ -80,26 +76,19 @@ debug("find", session, first); ...@@ -80,26 +76,19 @@ debug("find", session, first);
} }
private boolean removeIfExists(Session session, Row row) throws SQLException { private boolean removeIfExists(Session session, Row row) throws SQLException {
int test;
debug("removeIfExists ", session, row);
// maybe it was inserted by the same session just before // maybe it was inserted by the same session just before
Cursor c = delta.find(session, row, row); Cursor c = delta.find(session, row, row);
while (c.next()) { while (c.next()) {
Row r = c.get(); Row r = c.get();
if (r.getPos() == row.getPos()) { if (r.getPos() == row.getPos()) {
debug(" >remove", session, null);
delta.remove(session, row); delta.remove(session, row);
debug(" >return true", session, null);
return true; return true;
} }
} }
debug(" >return false", session, null);
return false; return false;
} }
public void remove(Session session, Row row) throws SQLException { public void remove(Session session, Row row) throws SQLException {
int test;
debug("remove", session, row);
synchronized (sync) { synchronized (sync) {
base.remove(session, row); base.remove(session, row);
if (removeIfExists(session, row)) { if (removeIfExists(session, row)) {
...@@ -117,8 +106,6 @@ debug("remove", session, row); ...@@ -117,8 +106,6 @@ debug("remove", session, row);
} }
public void truncate(Session session) throws SQLException { public void truncate(Session session) throws SQLException {
int test;
debug("truncate", session, null);
synchronized (sync) { synchronized (sync) {
delta.truncate(session); delta.truncate(session);
base.truncate(session); base.truncate(session);
...@@ -126,8 +113,6 @@ debug("truncate", session, null); ...@@ -126,8 +113,6 @@ debug("truncate", session, null);
} }
public void commit(int operation, Row row) throws SQLException { public void commit(int operation, Row row) throws SQLException {
int test;
debug("commit", null, row);
synchronized (sync) { synchronized (sync) {
removeIfExists(null, row); removeIfExists(null, row);
} }
...@@ -273,7 +258,7 @@ debug("commit", null, row); ...@@ -273,7 +258,7 @@ debug("commit", null, row);
} }
void debug(String s, Session session, SearchRow row) throws SQLException { void debug(String s, Session session, SearchRow row) throws SQLException {
// System.out.println(this + " " + s + " sess:" + (session == null ? -1: session.getId()) + " " + (row == null ? "" : row.getValue(0).getString())); // System.out.println(this + " " + s + " sess:" + (session == null ? -1: session.getId()) + " " + (row == null ? "" : row.getValue(0).getString()));
} }
} }
...@@ -66,7 +66,7 @@ public class RangeIndex extends BaseIndex { ...@@ -66,7 +66,7 @@ public class RangeIndex extends BaseIndex {
throw Message.getUnsupportedException(); throw Message.getUnsupportedException();
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return true; return true;
} }
......
...@@ -253,7 +253,7 @@ public class ScanIndex extends BaseIndex { ...@@ -253,7 +253,7 @@ public class ScanIndex extends BaseIndex {
return false; return false;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -341,7 +341,7 @@ public class TreeIndex extends BaseIndex { ...@@ -341,7 +341,7 @@ public class TreeIndex extends BaseIndex {
return true; return true;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return true; return true;
} }
......
...@@ -21,6 +21,11 @@ import org.h2.util.ObjectArray; ...@@ -21,6 +21,11 @@ import org.h2.util.ObjectArray;
import org.h2.util.SmallLRUCache; import org.h2.util.SmallLRUCache;
import org.h2.value.Value; import org.h2.value.Value;
/**
* This object represents a virtual index for a query.
* Actually it only represents a prepared statement that is
* a SELECT statement.
*/
public class ViewIndex extends BaseIndex { public class ViewIndex extends BaseIndex {
private String querySQL; private String querySQL;
...@@ -233,7 +238,7 @@ public class ViewIndex extends BaseIndex { ...@@ -233,7 +238,7 @@ public class ViewIndex extends BaseIndex {
return false; return false;
} }
public boolean canGetFirstOrLast(boolean first) { public boolean canGetFirstOrLast() {
return false; return false;
} }
......
...@@ -420,41 +420,41 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat ...@@ -420,41 +420,41 @@ public class JdbcDatabaseMetaData extends TraceObject implements DatabaseMetaDat
/** /**
* Checks is NULL values are sorted high (bigger than any non-null values). * Checks is NULL values are sorted high (bigger than any non-null values).
* *
* @return true or false * @return false
*/ */
public boolean nullsAreSortedHigh() { public boolean nullsAreSortedHigh() {
debugCodeCall("nullsAreSortedHigh"); debugCodeCall("nullsAreSortedHigh");
return Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_HIGH; return false;
} }
/** /**
* Checks is NULL values are sorted low (smaller than any non-null values). * Checks is NULL values are sorted low (smaller than any non-null values).
* *
* @return true or false * @return true
*/ */
public boolean nullsAreSortedLow() { public boolean nullsAreSortedLow() {
debugCodeCall("nullsAreSortedLow"); debugCodeCall("nullsAreSortedLow");
return Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_LOW; return true;
} }
/** /**
* Checks is NULL values are sorted at the beginning (no matter if ASC or DESC is used). * Checks is NULL values are sorted at the beginning (no matter if ASC or DESC is used).
* *
* @return true or false * @return false
*/ */
public boolean nullsAreSortedAtStart() { public boolean nullsAreSortedAtStart() {
debugCodeCall("nullsAreSortedAtStart"); debugCodeCall("nullsAreSortedAtStart");
return Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_START; return false;
} }
/** /**
* Checks is NULL values are sorted at the end (no matter if ASC or DESC is used). * Checks is NULL values are sorted at the end (no matter if ASC or DESC is used).
* *
* @return true or false * @return false
*/ */
public boolean nullsAreSortedAtEnd() { public boolean nullsAreSortedAtEnd() {
debugCodeCall("nullsAreSortedAtEnd"); debugCodeCall("nullsAreSortedAtEnd");
return Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_END; return false;
} }
/** /**
......
...@@ -6,7 +6,6 @@ package org.h2.result; ...@@ -6,7 +6,6 @@ package org.h2.result;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
...@@ -15,10 +14,10 @@ import org.h2.util.StringUtils; ...@@ -15,10 +14,10 @@ import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* @author Thomas * A sort order represents an ORDER BY clause in a query.
*/ */
public class SortOrder { public class SortOrder {
public static final int ASCENDING = 0, DESCENDING = 1; public static final int ASCENDING = 0, DESCENDING = 1;
public static final int NULLS_FIRST = 2, NULLS_LAST = 4; public static final int NULLS_FIRST = 2, NULLS_LAST = 4;
...@@ -61,38 +60,32 @@ public class SortOrder { ...@@ -61,38 +60,32 @@ public class SortOrder {
return buff.toString(); return buff.toString();
} }
public int compare(Value[] a, Value[] b) throws SQLException { public static int compareNull(boolean aNull, boolean bNull, int type) {
for (int i = 0; i < len; i++) {
int idx = indexes[i];
int type = sortTypes[i];
Value o1 = a[idx];
Value o2 = b[idx];
boolean b1 = o1 == ValueNull.INSTANCE, b2 = o2 == ValueNull.INSTANCE;
if (b1 || b2) {
if (b1 == b2) {
continue;
}
if ((type & NULLS_FIRST) != 0) { if ((type & NULLS_FIRST) != 0) {
return b1 ? -1 : 1; return aNull ? -1 : 1;
} else if ((type & NULLS_LAST) != 0) { } else if ((type & NULLS_LAST) != 0) {
return b1 ? 1 : -1; return aNull ? 1 : -1;
} else { } else {
// this depends on NULL_SORT_DEFAULT // see also JdbcDatabaseMetaData.nullsAreSorted*
int comp; int comp = aNull ? -1 : 1;
if (Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_LOW) {
comp = b1 ? -1 : 1;
return (type & DESCENDING) == 0 ? comp : -comp;
} else if (Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_HIGH) {
comp = b1 ? 1 : -1;
return (type & DESCENDING) == 0 ? comp : -comp; return (type & DESCENDING) == 0 ? comp : -comp;
} else if (Constants.NULL_SORT_DEFAULT == Constants.NULL_SORT_START) {
return b1 ? 1 : -1;
} else {
return b1 ? -1 : 1;
} }
} }
public int compare(Value[] a, Value[] b) throws SQLException {
for (int i = 0; i < len; i++) {
int idx = indexes[i];
int type = sortTypes[i];
Value ao = a[idx];
Value bo = b[idx];
boolean aNull = ao == ValueNull.INSTANCE, bNull = bo == ValueNull.INSTANCE;
if (aNull || bNull) {
if (aNull == bNull) {
continue;
}
return compareNull(aNull, bNull, type);
} }
int comp = database.compare(o1, o2); int comp = database.compare(ao, bo);
if (comp != 0) { if (comp != 0) {
return (type & DESCENDING) == 0 ? comp : -comp; return (type & DESCENDING) == 0 ? comp : -comp;
} }
......
...@@ -33,7 +33,7 @@ import org.h2.value.ValueTimestamp; ...@@ -33,7 +33,7 @@ import org.h2.value.ValueTimestamp;
import org.h2.value.ValueUuid; import org.h2.value.ValueUuid;
/** /**
* @author Thomas * This class represents a column in a table.
*/ */
public class Column { public class Column {
private final int type; private final int type;
......
...@@ -9,6 +9,10 @@ import java.sql.SQLException; ...@@ -9,6 +9,10 @@ import java.sql.SQLException;
import org.h2.command.dml.Select; import org.h2.command.dml.Select;
import org.h2.value.Value; import org.h2.value.Value;
/**
* A column resolver is list of column (for example, a table) that can map a
* column name to an actual column.
*/
public interface ColumnResolver { public interface ColumnResolver {
String getTableAlias(); String getTableAlias();
......
...@@ -6,16 +6,27 @@ package org.h2.table; ...@@ -6,16 +6,27 @@ package org.h2.table;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.result.SortOrder;
/**
* This represents a column item of an index. This is required because some
* indexes support descending sorted columns.
*/
public class IndexColumn { public class IndexColumn {
public String columnName; public String columnName;
public Column column; public Column column;
public boolean descending; public int sortType = SortOrder.ASCENDING;
public String getSQL() { public String getSQL() {
StringBuffer buff = new StringBuffer(column.getSQL()); StringBuffer buff = new StringBuffer(column.getSQL());
if (descending) { if ((sortType & SortOrder.DESCENDING) != 0) {
buff.append(" DESC"); buff.append(" DESC");
} }
if ((sortType & SortOrder.NULLS_FIRST) != 0) {
buff.append(" NULLS FIRST");
} else if ((sortType & SortOrder.NULLS_LAST) != 0) {
buff.append(" NULLS LAST");
}
return buff.toString(); return buff.toString();
} }
......
...@@ -22,6 +22,7 @@ import org.h2.result.Row; ...@@ -22,6 +22,7 @@ import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SimpleRow; import org.h2.result.SimpleRow;
import org.h2.result.SimpleRowValue; import org.h2.result.SimpleRowValue;
import org.h2.result.SortOrder;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.schema.SchemaObjectBase; import org.h2.schema.SchemaObjectBase;
import org.h2.schema.Sequence; import org.h2.schema.Sequence;
...@@ -32,9 +33,8 @@ import org.h2.value.Value; ...@@ -32,9 +33,8 @@ import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* @author Thomas * A table contains a list of columns and a list of rows.
*/ */
public abstract class Table extends SchemaObjectBase { public abstract class Table extends SchemaObjectBase {
public static final int TYPE_CACHED = 0, TYPE_MEMORY = 1; public static final int TYPE_CACHED = 0, TYPE_MEMORY = 1;
...@@ -489,7 +489,12 @@ public abstract class Table extends SchemaObjectBase { ...@@ -489,7 +489,12 @@ public abstract class Table extends SchemaObjectBase {
ObjectArray indexes = getIndexes(); ObjectArray indexes = getIndexes();
for (int i = 1; indexes != null && i < indexes.size(); i++) { for (int i = 1; indexes != null && i < indexes.size(); i++) {
Index index = (Index) indexes.get(i); Index index = (Index) indexes.get(i);
if (index.canGetFirstOrLast(first)) { if (index.canGetFirstOrLast()) {
IndexColumn idxCol = index.getIndexColumns()[0];
if ((idxCol.sortType & SortOrder.DESCENDING) != 0 && (idxCol.sortType & SortOrder.NULLS_FIRST) == 0) {
// for descending sorted columns, if the NULLs are at the end, it does not work for some index types
continue;
}
int idx = index.getColumnIndex(column); int idx = index.getColumnIndex(column);
if (idx == 0) { if (idx == 0) {
return index; return index;
......
...@@ -20,13 +20,16 @@ import org.h2.index.IndexCondition; ...@@ -20,13 +20,16 @@ import org.h2.index.IndexCondition;
import org.h2.message.Message; import org.h2.message.Message;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
/** /**
* @author Thomas * A table filter represents a table that is used in a query. There is one such object whenever a table
* (or view) is used in a query. For example the following query has 2 table filters:
* SELECT * FROM TEST T1, TEST T2.
*/ */
public class TableFilter implements ColumnResolver { public class TableFilter implements ColumnResolver {
private static final int BEFORE_FIRST = 0, FOUND = 1, AFTER_LAST = 2, NULL_ROW = 3; private static final int BEFORE_FIRST = 0, FOUND = 1, AFTER_LAST = 2, NULL_ROW = 3;
...@@ -35,6 +38,7 @@ public class TableFilter implements ColumnResolver { ...@@ -35,6 +38,7 @@ public class TableFilter implements ColumnResolver {
private final Select select; private final Select select;
private Session session; private Session session;
private Index index; private Index index;
private IndexColumn[] indexColumns;
private Cursor cursor; private Cursor cursor;
private int scanCount; private int scanCount;
private boolean used; // used in the plan private boolean used; // used in the plan
...@@ -134,7 +138,7 @@ public class TableFilter implements ColumnResolver { ...@@ -134,7 +138,7 @@ public class TableFilter implements ColumnResolver {
} }
public void setPlanItem(PlanItem item) { public void setPlanItem(PlanItem item) {
this.index = item.getIndex(); setIndex(item.getIndex());
for (int i = 0; joins != null && i < joins.size(); i++) { for (int i = 0; joins != null && i < joins.size(); i++) {
TableFilter join = getTableFilter(i); TableFilter join = getTableFilter(i);
if (item.getJoinPlan() != null) { if (item.getJoinPlan() != null) {
...@@ -204,17 +208,24 @@ public class TableFilter implements ColumnResolver { ...@@ -204,17 +208,24 @@ public class TableFilter implements ColumnResolver {
int type = column.getType(); int type = column.getType();
int id = column.getColumnId(); int id = column.getColumnId();
Value v = condition.getCurrentValue(session).convertTo(type); Value v = condition.getCurrentValue(session).convertTo(type);
if (condition.isStart()) { boolean isStart = condition.isStart(), isEnd = condition.isEnd();
// TODO index: start.setExpression(id, IndexColumn idxCol = indexColumns[id];
// bigger(start.getValue(id), e)); if (idxCol != null && (idxCol.sortType & SortOrder.DESCENDING) != 0) {
// if the index column is sorted the other way, we swap end and start
// NULLS_FIRST / NULLS_LAST is not a problem, as nulls never match anyway
boolean temp = isStart;
isStart = isEnd;
isEnd = temp;
}
if (isStart) {
// TODO index: start.setExpression(id, bigger(start.getValue(id), e));
if (start == null) { if (start == null) {
start = table.getTemplateRow(); start = table.getTemplateRow();
} }
start.setValue(id, v); start.setValue(id, v);
} }
if (condition.isEnd()) { if (isEnd) {
// TODO index: end.setExpression(id, // TODO index: end.setExpression(id, smaller(end.getExpression(id), e));
// smaller(end.getExpression(id), e));
if (end == null) { if (end == null) {
end = table.getTemplateRow(); end = table.getTemplateRow();
} }
...@@ -459,6 +470,17 @@ public class TableFilter implements ColumnResolver { ...@@ -459,6 +470,17 @@ public class TableFilter implements ColumnResolver {
public void setIndex(Index index) { public void setIndex(Index index) {
this.index = index; this.index = index;
Column[] columns = table.getColumns();
indexColumns = new IndexColumn[columns.length];
IndexColumn[] idxCols = index.getIndexColumns();
if (idxCols != null) {
for (int i = 0; i < columns.length; i++) {
int idx = index.getColumnIndex(columns[i]);
if (idx >= 0) {
indexColumns[i] = idxCols[idx];
}
}
}
} }
public void setUsed(boolean used) { public void setUsed(boolean used) {
......
...@@ -7,7 +7,6 @@ package org.h2.test; ...@@ -7,7 +7,6 @@ package org.h2.test;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import org.h2.constant.SysProperties;
import org.h2.server.TcpServer; import org.h2.server.TcpServer;
import org.h2.test.db.TestAutoRecompile; import org.h2.test.db.TestAutoRecompile;
import org.h2.test.db.TestBackup; import org.h2.test.db.TestBackup;
...@@ -142,10 +141,12 @@ java org.h2.test.TestAll timer ...@@ -142,10 +141,12 @@ java org.h2.test.TestAll timer
/* /*
---- ----
A file is sent although the Japanese translation has not been completed yet. A file is sent although the Japanese translation has not been completed yet.
---- ----
reverse index
Code coverage Code coverage
At startup, when corrupted, say if LOG=0 was used before At startup, when corrupted, say if LOG=0 was used before
......
...@@ -41,6 +41,7 @@ public class TestCluster extends TestBase { ...@@ -41,6 +41,7 @@ public class TestCluster extends TestBase {
prep.setString(2, "Data" + i); prep.setString(2, "Data" + i);
prep.executeUpdate(); prep.executeUpdate();
} }
check(conn, len);
conn.close(); conn.close();
CreateCluster.main(new String[] { "-urlSource", "jdbc:h2:file:" + baseDir + "/node1/test", "-urlTarget", CreateCluster.main(new String[] { "-urlSource", "jdbc:h2:file:" + baseDir + "/node1/test", "-urlTarget",
......
CREATE TABLE TEST(ID INT);
INSERT INTO TEST VALUES(1), (2), (3);
create index idxdesc on test(id desc);
select * from test where id between 0 and 1;
> 1;
select * from test where id between 3 and 4;
> 3;
drop table test;
CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255));
INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld'); INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld');
SELECT COUNT(*) FROM TEST WHERE NAME REGEXP 'World'; SELECT COUNT(*) FROM TEST WHERE NAME REGEXP 'World';
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论