提交 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)
"
"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 [,...])
","
Creates a new index.
......@@ -452,7 +452,7 @@ that name may exist in the target database.
Usually, for update statements, the old rows are deleted first and then the new
rows are inserted. It is possible to emit update statements (except on
rollback), however in this case multi-row unique key updates may not always
work. Linked tables to the same database share one connection.
work. Linked tables to the same database share one connection.
If a query is used instead of the original table name, the table is read only.
Queries must be enclosed in parenthesis: (SELECT * FROM ORDERS).
......
......@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1>
<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>
<h2>Version 1.1.118 (2009-09-04)</h2>
......
......@@ -3492,9 +3492,9 @@ public class Parser {
} else {
if (readIf("UNIQUE")) {
unique = true;
if (readIf("HASH")) {
hash = true;
}
}
if (readIf("HASH")) {
hash = true;
}
if (readIf("INDEX")) {
if (!isToken("ON")) {
......
......@@ -83,7 +83,7 @@ public class CreateIndex extends SchemaCommand {
} else if (unique) {
indexType = IndexType.createUnique(persistent, hash);
} else {
indexType = IndexType.createNonUnique(persistent);
indexType = IndexType.createNonUnique(persistent, hash);
}
IndexColumn.mapColumns(indexColumns, table);
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;
import org.h2.message.Message;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableData;
import org.h2.util.IntIntHashMap;
import org.h2.util.ValueHashMap;
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 IntIntHashMap intMap;
private TableData tableData;
private long rowCount;
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;
reset();
}
......@@ -43,18 +40,10 @@ public class HashIndex extends BaseIndex {
}
}
public void close(Session session) {
// nothing to do
}
public void truncate(Session session) {
reset();
}
public void remove(Session session) {
// nothing to do
}
public void add(Session session, Row row) throws SQLException {
if (intMap != null) {
int key = row.getValue(columns[0].getColumnId()).getInt();
......@@ -68,7 +57,6 @@ public class HashIndex extends BaseIndex {
}
rows.put(getKey(row), row.getPos());
}
rowCount++;
}
public void remove(Session session, Row row) throws SQLException {
......@@ -78,23 +66,6 @@ public class HashIndex extends BaseIndex {
} else {
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 {
......@@ -122,39 +93,12 @@ public class HashIndex extends BaseIndex {
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) {
return rowCount;
return getRowCountApproximation();
}
public long getRowCountApproximation() {
return rowCount;
return intMap != null ? intMap.size() : rows.size();
}
}
......@@ -51,8 +51,20 @@ public class IndexType {
* @return the index type
*/
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();
type.persistent = persistent;
type.hash = hash;
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]
","
Creates a new data type (domain)."
"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 [,...])
","
Creates a new index."
......@@ -790,7 +790,7 @@ BOOL_OR(boolean): boolean
","
Returns true if any expression is true."
"Functions (Aggregate)","COUNT","
COUNT(*) | COUNT([DISTINCT] expression): int
COUNT(*) | COUNT([DISTINCT] expression): long
","
The count of all row, or of the non-null values."
"Functions (Aggregate)","GROUP_CONCAT","
......
......@@ -25,6 +25,7 @@ import org.h2.index.HashIndex;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.index.MultiVersionIndex;
import org.h2.index.NonUniqueHashIndex;
import org.h2.index.PageBtreeIndex;
import org.h2.index.PageScanIndex;
import org.h2.index.RowIndex;
......@@ -193,7 +194,11 @@ public class TableData extends Table implements RecordReader {
}
} else {
if (indexType.isHash()) {
index = new HashIndex(this, indexId, indexName, cols, indexType);
if (indexType.isUnique()) {
index = new HashIndex(this, indexId, indexName, cols, indexType);
} else {
index = new NonUniqueHashIndex(this, indexId, indexName, cols, indexType);
}
} else {
index = new TreeIndex(this, indexId, indexName, cols, indexType);
}
......
......@@ -25,6 +25,13 @@ public class IntArray {
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.
*/
......
......@@ -43,7 +43,7 @@ public class ObjectArray<T> implements Iterable<T> {
* @return the object
*/
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';
> test;
drop table test2;
@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
daload dstore saload anewarray tableswitch lushr ladd lshr lreturn acmpne
locals multianewarray icmpne fneg faload ifeq decompiler zeroes forgot
modern slight boost characteristics significantly gae vfs centrally ten
approach risky
\ No newline at end of file
approach risky getters
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论