/*
* Copyright 2004-2011 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 org.h2.constant.SysProperties;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.IndexColumn;
import org.h2.table.RegularTable;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* The tree index is an in-memory index based on a binary AVL trees.
*/
public class TreeIndex extends BaseIndex {
private TreeNode root;
private RegularTable tableData;
private long rowCount;
private boolean closed;
public TreeIndex(RegularTable table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
initBaseIndex(table, id, indexName, columns, indexType);
tableData = table;
}
public void close(Session session) {
root = null;
closed = true;
}
public void add(Session session, Row row) {
if (closed) {
throw DbException.throwInternalError();
}
TreeNode i = new TreeNode(row);
TreeNode n = root, x = n;
boolean isLeft = true;
while (true) {
if (n == null) {
if (x == null) {
root = i;
rowCount++;
return;
}
set(x, isLeft, i);
break;
}
Row r = n.row;
int compare = compareRows(row, r);
if (compare == 0) {
if (indexType.isUnique()) {
if (!containsNullAndAllowMultipleNull(row)) {
throw getDuplicateKeyException();
}
}
compare = compareKeys(row, r);
}
isLeft = compare < 0;
x = n;
n = child(x, isLeft);
}
balance(x, isLeft);
rowCount++;
}
private void balance(TreeNode x, boolean isLeft) {
while (true) {
int sign = isLeft ? 1 : -1;
switch (x.balance * sign) {
case 1:
x.balance = 0;
return;
case 0:
x.balance = -sign;
break;
case -1:
TreeNode l = child(x, isLeft);
if (l.balance == -sign) {
replace(x, l);
set(x, isLeft, child(l, !isLeft));
set(l, !isLeft, x);
x.balance = 0;
l.balance = 0;
} else {
TreeNode r = child(l, !isLeft);
replace(x, r);
set(l, !isLeft, child(r, isLeft));
set(r, isLeft, l);
set(x, isLeft, child(r, !isLeft));
set(r, !isLeft, x);
int rb = r.balance;
x.balance = (rb == -sign) ? sign : 0;
l.balance = (rb == sign) ? -sign : 0;
r.balance = 0;
}
return;
default:
DbException.throwInternalError("b:" + x.balance * sign);
}
if (x == root) {
return;
}
isLeft = x.isFromLeft();
x = x.parent;
}
}
private static TreeNode child(TreeNode x, boolean isLeft) {
return isLeft ? x.left : x.right;
}
private void replace(TreeNode x, TreeNode n) {
if (x == root) {
root = n;
if (n != null) {
n.parent = null;
}
} else {
set(x.parent, x.isFromLeft(), n);
}
}
private static void set(TreeNode parent, boolean left, TreeNode n) {
if (left) {
parent.left = n;
} else {
parent.right = n;
}
if (n != null) {
n.parent = parent;
}
}
public void remove(Session session, Row row) {
if (closed) {
throw DbException.throwInternalError();
}
TreeNode x = findFirstNode(row, true);
if (x == null) {
throw DbException.throwInternalError("not found!");
}
TreeNode n;
if (x.left == null) {
n = x.right;
} else if (x.right == null) {
n = x.left;
} else {
TreeNode d = x;
x = x.left;
for (TreeNode temp = x; (temp = temp.right) != null;) {
x = temp;
}
// x will be replaced with n later
n = x.left;
// swap d and x
int b = x.balance;
x.balance = d.balance;
d.balance = b;
// set x.parent
TreeNode xp = x.parent;
TreeNode dp = d.parent;
if (d == root) {
root = x;
}
x.parent = dp;
if (dp != null) {
if (dp.right == d) {
dp.right = x;
} else {
dp.left = x;
}
}
// TODO index / tree: link d.r = x(p?).r directly
if (xp == d) {
d.parent = x;
if (d.left == x) {
x.left = d;
x.right = d.right;
} else {
x.right = d;
x.left = d.left;
}
} else {
d.parent = xp;
xp.right = d;
x.right = d.right;
x.left = d.left;
}
if (SysProperties.CHECK && x.right == null) {
DbException.throwInternalError("tree corrupted");
}
x.right.parent = x;
x.left.parent = x;
// set d.left, d.right
d.left = n;
if (n != null) {
n.parent = d;
}
d.right = null;
x = d;
}
rowCount--;
boolean isLeft = x.isFromLeft();
replace(x, n);
n = x.parent;
while (n != null) {
x = n;
int sign = isLeft ? 1 : -1;
switch (x.balance * sign) {
case -1:
x.balance = 0;
break;
case 0:
x.balance = sign;
return;
case 1:
TreeNode r = child(x, !isLeft);
int b = r.balance;
if (b * sign >= 0) {
replace(x, r);
set(x, !isLeft, child(r, isLeft));
set(r, isLeft, x);
if (b == 0) {
x.balance = sign;
r.balance = -sign;
return;
}
x.balance = 0;
r.balance = 0;
x = r;
} else {
TreeNode l = child(r, isLeft);
replace(x, l);
b = l.balance;
set(r, isLeft, child(l, !isLeft));
set(l, !isLeft, r);
set(x, !isLeft, child(l, isLeft));
set(l, isLeft, x);
x.balance = (b == sign) ? -sign : 0;
r.balance = (b == -sign) ? sign : 0;
l.balance = 0;
x = l;
}
break;
default:
DbException.throwInternalError("b: " + x.balance * sign);
}
isLeft = x.isFromLeft();
n = x.parent;
}
}
private TreeNode findFirstNode(SearchRow row, boolean withKey) {
TreeNode x = root, result = x;
while (x != null) {
result = x;
int compare = compareRows(x.row, row);
if (compare == 0 && withKey) {
compare = compareKeys(x.row, row);
}
if (compare == 0) {
if (withKey) {
return x;
}
x = x.left;
} else if (compare > 0) {
x = x.left;
} else {
x = x.right;
}
}
return result;
}
public Cursor find(TableFilter filter, SearchRow first, SearchRow last) {
return find(first, last);
}
public Cursor find(Session session, SearchRow first, SearchRow last) {
return find(first, last);
}
private Cursor find(SearchRow first, SearchRow last) {
if (first == null) {
TreeNode x = root, n;
while (x != null) {
n = x.left;
if (n == null) {
break;
}
x = n;
}
return new TreeCursor(this, x, null, last);
}
TreeNode x = findFirstNode(first, false);
return new TreeCursor(this, x, first, last);
}
public double getCost(Session session, int[] masks) {
return getCostRangeIndex(masks, tableData.getRowCountApproximation());
}
public void remove(Session session) {
truncate(session);
}
public void truncate(Session session) {
root = null;
rowCount = 0;
}
public void checkRename() {
// nothing to do
}
public boolean needRebuild() {
return true;
}
public boolean canGetFirstOrLast() {
return true;
}
public Cursor findFirstOrLast(Session session, boolean first) {
if (closed) {
throw DbException.throwInternalError();
}
if (first) {
// TODO optimization: this loops through NULL
Cursor cursor = find(session, null, null);
while (cursor.next()) {
SearchRow row = cursor.getSearchRow();
Value v = row.getValue(columnIds[0]);
if (v != ValueNull.INSTANCE) {
return cursor;
}
}
return cursor;
}
TreeNode x = root, n;
while (x != null) {
n = x.right;
if (n == null) {
break;
}
x = n;
}
TreeCursor cursor = new TreeCursor(this, x, null, null);
if (x == null) {
return cursor;
}
// TODO optimization: this loops through NULL elements
do {
SearchRow row = cursor.getSearchRow();
if (row == null) {
break;
}
Value v = row.getValue(columnIds[0]);
if (v != ValueNull.INSTANCE) {
return cursor;
}
} while (cursor.previous());
return cursor;
}
public long getRowCount(Session session) {
return rowCount;
}
public long getRowCountApproximation() {
return rowCount;
}
}