提交 827b5094 authored 作者: Thomas Mueller's avatar Thomas Mueller

Non-unique hash indexes are now supported. Thanks a lot to Sergi Vladykin for the patch!

上级 efe1c7f4
...@@ -431,7 +431,7 @@ CREATE DOMAIN EMAIL AS VARCHAR(255) CHECK (POSITION('@', VALUE) > 1) ...@@ -431,7 +431,7 @@ CREATE DOMAIN EMAIL AS VARCHAR(255) CHECK (POSITION('@', VALUE) > 1)
" "
"Commands (DDL)","CREATE INDEX"," "Commands (DDL)","CREATE INDEX","
CREATE {[UNIQUE [HASH]] INDEX [IF NOT EXISTS] newIndexName CREATE {[UNIQUE] [HASH] INDEX [IF NOT EXISTS] newIndexName
| PRIMARY KEY [HASH]} ON tableName(indexColumn [,...]) | PRIMARY KEY [HASH]} ON tableName(indexColumn [,...])
"," ","
Creates a new index. Creates a new index.
......
...@@ -18,7 +18,8 @@ Change Log ...@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>The optimizer does a better job for joins if indexes are missing. <ul><li>Non-unique hash indexes are now supported. Thanks a lot to Sergi Vladykin for the patch!
</li><li>The optimizer does a better job for joins if indexes are missing.
</li></ul> </li></ul>
<h2>Version 1.1.118 (2009-09-04)</h2> <h2>Version 1.1.118 (2009-09-04)</h2>
......
...@@ -3492,10 +3492,10 @@ public class Parser { ...@@ -3492,10 +3492,10 @@ public class Parser {
} else { } else {
if (readIf("UNIQUE")) { if (readIf("UNIQUE")) {
unique = true; unique = true;
}
if (readIf("HASH")) { if (readIf("HASH")) {
hash = true; hash = true;
} }
}
if (readIf("INDEX")) { if (readIf("INDEX")) {
if (!isToken("ON")) { if (!isToken("ON")) {
ifNotExists = readIfNoExists(); ifNotExists = readIfNoExists();
......
...@@ -83,7 +83,7 @@ public class CreateIndex extends SchemaCommand { ...@@ -83,7 +83,7 @@ public class CreateIndex extends SchemaCommand {
} else if (unique) { } else if (unique) {
indexType = IndexType.createUnique(persistent, hash); indexType = IndexType.createUnique(persistent, hash);
} else { } else {
indexType = IndexType.createNonUnique(persistent); indexType = IndexType.createNonUnique(persistent, hash);
} }
IndexColumn.mapColumns(indexColumns, table); IndexColumn.mapColumns(indexColumns, table);
table.addIndex(session, indexName, id, indexColumns, indexType, headPos, comment); table.addIndex(session, indexName, id, indexColumns, indexType, headPos, comment);
......
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.index;
import java.sql.SQLException;
import org.h2.engine.Session;
import org.h2.message.Message;
import org.h2.result.SearchRow;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.value.Value;
import org.h2.value.ValueArray;
/**
* Base of hash indexes.
*/
public abstract class BaseHashIndex extends BaseIndex {
public BaseHashIndex(TableData table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
initBaseIndex(table, id, indexName, columns, indexType);
}
public void close(Session session) {
// nothing to do
}
public void remove(Session session) {
// nothing to do
}
/**
* Generate the search key from a row. Single column indexes are mapped to
* the value, multi-column indexes are mapped to an value array.
*
* @param row the row
* @return the value
*/
protected Value getKey(SearchRow row) {
if (columns.length == 1) {
Column column = columns[0];
int index = column.getColumnId();
Value v = row.getValue(index);
return v;
}
Value[] list = new Value[columns.length];
for (int i = 0; i < columns.length; i++) {
Column column = columns[i];
int index = column.getColumnId();
list[i] = row.getValue(index);
}
return ValueArray.get(list);
}
public double getCost(Session session, int[] masks) {
for (Column column : columns) {
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.EQUALITY) != IndexCondition.EQUALITY) {
return Long.MAX_VALUE;
}
}
return 2;
}
public void checkRename() {
// ok
}
public boolean needRebuild() {
return true;
}
public boolean canGetFirstOrLast() {
return false;
}
public Cursor findFirstOrLast(Session session, boolean first) throws SQLException {
throw Message.getUnsupportedException("HASH");
}
}
...@@ -11,26 +11,23 @@ import org.h2.engine.Session; ...@@ -11,26 +11,23 @@ import org.h2.engine.Session;
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.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.TableData; import org.h2.table.TableData;
import org.h2.util.IntIntHashMap; import org.h2.util.IntIntHashMap;
import org.h2.util.ValueHashMap; import org.h2.util.ValueHashMap;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray;
/** /**
* An index based on an in-memory hash map. * An unique index based on an in-memory hash map.
*/ */
public class HashIndex extends BaseIndex { public class HashIndex extends BaseHashIndex {
private ValueHashMap<Integer> rows; private ValueHashMap<Integer> rows;
private IntIntHashMap intMap; private IntIntHashMap intMap;
private TableData tableData; private TableData tableData;
private long rowCount;
public HashIndex(TableData table, int id, String indexName, IndexColumn[] columns, IndexType indexType) { public HashIndex(TableData table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
initBaseIndex(table, id, indexName, columns, indexType); super(table, id, indexName, columns, indexType);
this.tableData = table; this.tableData = table;
reset(); reset();
} }
...@@ -43,18 +40,10 @@ public class HashIndex extends BaseIndex { ...@@ -43,18 +40,10 @@ public class HashIndex extends BaseIndex {
} }
} }
public void close(Session session) {
// nothing to do
}
public void truncate(Session session) { public void truncate(Session session) {
reset(); reset();
} }
public void remove(Session session) {
// nothing to do
}
public void add(Session session, Row row) throws SQLException { public void add(Session session, Row row) throws SQLException {
if (intMap != null) { if (intMap != null) {
int key = row.getValue(columns[0].getColumnId()).getInt(); int key = row.getValue(columns[0].getColumnId()).getInt();
...@@ -68,7 +57,6 @@ public class HashIndex extends BaseIndex { ...@@ -68,7 +57,6 @@ public class HashIndex extends BaseIndex {
} }
rows.put(getKey(row), row.getPos()); rows.put(getKey(row), row.getPos());
} }
rowCount++;
} }
public void remove(Session session, Row row) throws SQLException { public void remove(Session session, Row row) throws SQLException {
...@@ -78,23 +66,6 @@ public class HashIndex extends BaseIndex { ...@@ -78,23 +66,6 @@ public class HashIndex extends BaseIndex {
} else { } else {
rows.remove(getKey(row)); rows.remove(getKey(row));
} }
rowCount--;
}
private Value getKey(SearchRow row) {
if (columns.length == 1) {
Column column = columns[0];
int index = column.getColumnId();
Value v = row.getValue(index);
return v;
}
Value[] list = new Value[columns.length];
for (int i = 0; i < columns.length; i++) {
Column column = columns[i];
int index = column.getColumnId();
list[i] = row.getValue(index);
}
return ValueArray.get(list);
} }
public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException { public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
...@@ -122,39 +93,12 @@ public class HashIndex extends BaseIndex { ...@@ -122,39 +93,12 @@ public class HashIndex extends BaseIndex {
return new HashCursor(result); return new HashCursor(result);
} }
public double getCost(Session session, int[] masks) {
for (Column column : columns) {
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.EQUALITY) != IndexCondition.EQUALITY) {
return Long.MAX_VALUE;
}
}
return 2;
}
public void checkRename() {
// ok
}
public boolean needRebuild() {
return true;
}
public boolean canGetFirstOrLast() {
return false;
}
public Cursor findFirstOrLast(Session session, boolean first) throws SQLException {
throw Message.getUnsupportedException("HASH");
}
public long getRowCount(Session session) { public long getRowCount(Session session) {
return rowCount; return getRowCountApproximation();
} }
public long getRowCountApproximation() { public long getRowCountApproximation() {
return rowCount; return intMap != null ? intMap.size() : rows.size();
} }
} }
...@@ -51,8 +51,20 @@ public class IndexType { ...@@ -51,8 +51,20 @@ public class IndexType {
* @return the index type * @return the index type
*/ */
public static IndexType createNonUnique(boolean persistent) { public static IndexType createNonUnique(boolean persistent) {
return createNonUnique(persistent, false);
}
/**
* Create a non-unique index.
*
* @param persistent if the index is persistent
* @param hash if a hash index should be used
* @return the index type
*/
public static IndexType createNonUnique(boolean persistent, boolean hash) {
IndexType type = new IndexType(); IndexType type = new IndexType();
type.persistent = persistent; type.persistent = persistent;
type.hash = hash;
return type; return type;
} }
......
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.index;
import java.sql.SQLException;
import org.h2.util.IntArray;
import org.h2.engine.Session;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.TableData;
/**
* Cursor implementation for non-unique hash index
*
* @author Sergi Vladykin
*/
public class NonUniqueHashCursor implements Cursor {
private final Session session;
private final IntArray positions;
private final TableData tableData;
private int index = -1;
public NonUniqueHashCursor(Session session, TableData tableData, IntArray positions) {
this.session = session;
this.tableData = tableData;
this.positions = positions;
}
public Row get() throws SQLException {
if (index < 0 || index >= positions.size()) {
return null;
}
return tableData.getRow(session, positions.get(index));
}
public int getPos() {
return index;
}
public SearchRow getSearchRow() throws SQLException {
return get();
}
public boolean next() {
return positions != null && ++index < positions.size();
}
public boolean previous() {
return positions != null && --index >= 0;
}
}
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.index;
import java.sql.SQLException;
import org.h2.util.IntArray;
import org.h2.engine.Session;
import org.h2.message.Message;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.util.ValueHashMap;
import org.h2.value.Value;
/**
* A non-unique index based on an in-memory hash map.
*
* @author Sergi Vladykin
*/
public class NonUniqueHashIndex extends BaseHashIndex {
private ValueHashMap<IntArray> rows;
private TableData tableData;
private long rowCount;
public NonUniqueHashIndex(TableData table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
super(table, id, indexName, columns, indexType);
this.tableData = table;
reset();
}
private void reset() {
rows = ValueHashMap.newInstance(table.getDatabase());
rowCount = 0;
}
public void truncate(Session session) {
reset();
}
public void add(Session session, Row row) throws SQLException {
Value key = getKey(row);
IntArray positions = rows.get(key);
if (positions == null) {
positions = new IntArray(1);
rows.put(key, positions);
}
positions.add(row.getPos());
rowCount++;
}
public void remove(Session session, Row row) throws SQLException {
if (rowCount == 1) {
// last row in table
reset();
} else {
Value key = getKey(row);
IntArray positions = rows.get(key);
if (positions.size() == 1) {
// last row with such key
rows.remove(key);
} else {
positions.removeValue(row.getPos());
}
rowCount--;
}
}
public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
if (first == null || last == null) {
throw Message.throwInternalError();
}
if (first != last) {
if (compareKeys(first, last) != 0) {
throw Message.throwInternalError();
}
}
IntArray positions = rows.get(getKey(first));
return new NonUniqueHashCursor(session, tableData, positions);
}
public long getRowCount(Session session) {
return rowCount;
}
public long getRowCountApproximation() {
return rowCount;
}
}
...@@ -170,7 +170,7 @@ CREATE DOMAIN [IF NOT EXISTS] newDomainName AS dataType [DEFAULT expression] ...@@ -170,7 +170,7 @@ CREATE DOMAIN [IF NOT EXISTS] newDomainName AS dataType [DEFAULT expression]
"," ","
Creates a new data type (domain)." Creates a new data type (domain)."
"Commands (DDL)","CREATE INDEX"," "Commands (DDL)","CREATE INDEX","
CREATE {[UNIQUE [HASH]] INDEX [IF NOT EXISTS] newIndexName CREATE {[UNIQUE] [HASH] INDEX [IF NOT EXISTS] newIndexName
| PRIMARY KEY [HASH]} ON tableName(indexColumn [,...]) | PRIMARY KEY [HASH]} ON tableName(indexColumn [,...])
"," ","
Creates a new index." Creates a new index."
...@@ -790,7 +790,7 @@ BOOL_OR(boolean): boolean ...@@ -790,7 +790,7 @@ BOOL_OR(boolean): boolean
"," ","
Returns true if any expression is true." Returns true if any expression is true."
"Functions (Aggregate)","COUNT"," "Functions (Aggregate)","COUNT","
COUNT(*) | COUNT([DISTINCT] expression): int COUNT(*) | COUNT([DISTINCT] expression): long
"," ","
The count of all row, or of the non-null values." The count of all row, or of the non-null values."
"Functions (Aggregate)","GROUP_CONCAT"," "Functions (Aggregate)","GROUP_CONCAT","
......
...@@ -25,6 +25,7 @@ import org.h2.index.HashIndex; ...@@ -25,6 +25,7 @@ import org.h2.index.HashIndex;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.index.MultiVersionIndex; import org.h2.index.MultiVersionIndex;
import org.h2.index.NonUniqueHashIndex;
import org.h2.index.PageBtreeIndex; import org.h2.index.PageBtreeIndex;
import org.h2.index.PageScanIndex; import org.h2.index.PageScanIndex;
import org.h2.index.RowIndex; import org.h2.index.RowIndex;
...@@ -193,7 +194,11 @@ public class TableData extends Table implements RecordReader { ...@@ -193,7 +194,11 @@ public class TableData extends Table implements RecordReader {
} }
} else { } else {
if (indexType.isHash()) { if (indexType.isHash()) {
if (indexType.isUnique()) {
index = new HashIndex(this, indexId, indexName, cols, indexType); index = new HashIndex(this, indexId, indexName, cols, indexType);
} else {
index = new NonUniqueHashIndex(this, indexId, indexName, cols, indexType);
}
} else { } else {
index = new TreeIndex(this, indexId, indexName, cols, indexType); index = new TreeIndex(this, indexId, indexName, cols, indexType);
} }
......
...@@ -25,6 +25,13 @@ public class IntArray { ...@@ -25,6 +25,13 @@ public class IntArray {
data = new int[10]; data = new int[10];
} }
/**
* Create an int array with specified initial capacity.
*/
public IntArray(int capacity) {
data = new int[capacity];
}
/** /**
* Create an int array with the given values and size. * Create an int array with the given values and size.
*/ */
......
...@@ -43,7 +43,7 @@ public class ObjectArray<T> implements Iterable<T> { ...@@ -43,7 +43,7 @@ public class ObjectArray<T> implements Iterable<T> {
* @return the object * @return the object
*/ */
public static <T> ObjectArray<T> newInstance(int capacity) { public static <T> ObjectArray<T> newInstance(int capacity) {
return new ObjectArray<T>(CAPACITY_INIT); return new ObjectArray<T>(capacity);
} }
/** /**
......
...@@ -572,3 +572,33 @@ select remarks from information_schema.columns where table_name = 'TEST2'; ...@@ -572,3 +572,33 @@ select remarks from information_schema.columns where table_name = 'TEST2';
> test; > test;
drop table test2; drop table test2;
@reconnect; @reconnect;
create table test1 (a varchar(10));
create hash index x1 on test1(a);
insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd');
insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd');
insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd');
insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd');
select count(*) from test1 where a='abcaaaa';
> 4;
select count(*) from test1 where a='abcbbbb';
> 4;
@reconnect;
select count(*) from test1 where a='abccccc';
> 4;
select count(*) from test1 where a='abcdddd';
> 4;
update test1 set a='abccccc' where a='abcdddd';
select count(*) from test1 where a='abccccc';
> 8;
select count(*) from test1 where a='abcdddd';
> 0;
delete from test1 where a='abccccc';
select count(*) from test1 where a='abccccc';
> 0;
truncate table test1;
insert into test1 values ('abcaaaa');
insert into test1 values ('abcaaaa');
delete from test1;
drop table test1;
@reconnect;
\ No newline at end of file
...@@ -610,4 +610,4 @@ lrem lstore monitorexit lmul monitorenter fadd interpreting ishl istore dcmpg ...@@ -610,4 +610,4 @@ lrem lstore monitorexit lmul monitorenter fadd interpreting ishl istore dcmpg
daload dstore saload anewarray tableswitch lushr ladd lshr lreturn acmpne daload dstore saload anewarray tableswitch lushr ladd lshr lreturn acmpne
locals multianewarray icmpne fneg faload ifeq decompiler zeroes forgot locals multianewarray icmpne fneg faload ifeq decompiler zeroes forgot
modern slight boost characteristics significantly gae vfs centrally ten modern slight boost characteristics significantly gae vfs centrally ten
approach risky approach risky getters
\ No newline at end of file \ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论