package org.h2.mvstore.db;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.api.ErrorCode;
import org.h2.command.dml.AllColumnsForPlan;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.mvstore.tx.Transaction;
import org.h2.mvstore.tx.TransactionMap;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
public class MVPrimaryIndex extends BaseIndex {
private final MVTable mvTable;
private final String mapName;
private final TransactionMap<Value, Value> dataMap;
private final AtomicLong lastKey = new AtomicLong();
private int mainIndexColumn = SearchRow.ROWID_INDEX;
public MVPrimaryIndex(Database db, MVTable table, int id,
IndexColumn[] columns, IndexType indexType) {
super(table, id, table.getName() + "_DATA", columns, indexType);
this.mvTable = table;
int[] sortTypes = new int[columns.length];
for (int i = 0; i < columns.length; i++) {
sortTypes[i] = SortOrder.ASCENDING;
}
ValueDataType keyType = new ValueDataType();
ValueDataType valueType = new ValueDataType(db, sortTypes);
mapName = "table." + getId();
assert db.isStarting() || !db.getStore().getMvStore().getMetaMap().containsKey("name." + mapName);
Transaction t = mvTable.getTransactionBegin();
dataMap = t.openMap(mapName, keyType, valueType);
dataMap.map.setVolatile(!table.isPersistData() || !indexType.isPersistent());
t.commit();
Value k = dataMap.map.lastKey();
lastKey.set(k == null ? 0 : k.getLong());
}
@Override
public String getCreateSQL() {
return null;
}
@Override
public String getPlanSQL() {
return table.getSQL(new StringBuilder(), false).append(".tableScan").toString();
}
public void setMainIndexColumn(int mainIndexColumn) {
this.mainIndexColumn = mainIndexColumn;
}
public int getMainIndexColumn() {
return mainIndexColumn;
}
@Override
public void close(Session session) {
}
@Override
public void add(Session session, Row row) {
if (mainIndexColumn == SearchRow.ROWID_INDEX) {
if (row.getKey() == 0) {
row.setKey(lastKey.incrementAndGet());
}
} else {
long c = row.getValue(mainIndexColumn).getLong();
row.setKey(c);
}
if (mvTable.getContainsLargeObject()) {
for (int i = 0, len = row.getColumnCount(); i < len; i++) {
Value v = row.getValue(i);
Value v2 = v.copy(database, getId());
if (v2.isLinkedToTable()) {
session.removeAtCommitStop(v2);
}
if (v != v2) {
row.setValue(i, v2);
}
}
}
TransactionMap<Value, Value> map = getMap(session);
long rowKey = row.getKey();
Value key = ValueLong.get(rowKey);
try {
Value oldValue = map.putIfAbsent(key, ValueArray.get(row.getValueList()));
if (oldValue != null) {
StringBuilder builder = new StringBuilder("PRIMARY KEY ON ");
table.getSQL(builder, false);
if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) {
builder.append('(');
indexColumns[mainIndexColumn].getSQL(builder, false).append(')');
}
int errorCode = ErrorCode.CONCURRENT_UPDATE_1;
if (map.get(key) != null) {
errorCode = ErrorCode.DUPLICATE_KEY_1;
}
builder.append(' ').append(oldValue);
DbException e = DbException.get(errorCode, builder.toString());
e.setSource(this);
throw e;
}
} catch (IllegalStateException e) {
throw mvTable.convertException(e);
}
long last;
while (rowKey > (last = lastKey.get())) {
if(lastKey.compareAndSet(last, rowKey)) break;
}
}
@Override
public void remove(Session session, Row row) {
if (mvTable.getContainsLargeObject()) {
for (int i = 0, len = row.getColumnCount(); i < len; i++) {
Value v = row.getValue(i);
if (v.isLinkedToTable()) {
session.removeAtCommit(v);
}
}
}
TransactionMap<Value, Value> map = getMap(session);
try {
Value old = map.remove(ValueLong.get(row.getKey()));
if (old == null) {
StringBuilder builder = new StringBuilder();
getSQL(builder, false).append(": ").append(row.getKey());
throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString());
}
} catch (IllegalStateException e) {
throw mvTable.convertException(e);
}
}
@Override
public void update(Session session, Row oldRow, Row newRow) {
if (mainIndexColumn != SearchRow.ROWID_INDEX) {
long c = newRow.getValue(mainIndexColumn).getLong();
newRow.setKey(c);
}
long key = oldRow.getKey();
assert mainIndexColumn != SearchRow.ROWID_INDEX || key != 0;
assert key == newRow.getKey() : key + " != " + newRow.getKey();
if (mvTable.getContainsLargeObject()) {
for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) {
Value oldValue = oldRow.getValue(i);
Value newValue = newRow.getValue(i);
if(oldValue != newValue) {
if (oldValue.isLinkedToTable()) {
session.removeAtCommit(oldValue);
}
Value v2 = newValue.copy(database, getId());
if (v2.isLinkedToTable()) {
session.removeAtCommitStop(v2);
}
if (newValue != v2) {
newRow.setValue(i, v2);
}
}
}
}
TransactionMap<Value,Value> map = getMap(session);
try {
Value existing = map.put(ValueLong.get(key), ValueArray.get(newRow.getValueList()));
if (existing == null) {
StringBuilder builder = new StringBuilder();
getSQL(builder, false).append(": ").append(key);
throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString());
}
} catch (IllegalStateException e) {
throw mvTable.convertException(e);
}
if (newRow.getKey() > lastKey.get()) {
lastKey.set(newRow.getKey());
}
}
Row lockRow(Session session, Row row) {
TransactionMap<Value, Value> map = getMap(session);
long key = row.getKey();
ValueArray array = (ValueArray) lockRow(map, key);
return array == null ? null : getRow(session, key, array);
}
private Value lockRow(TransactionMap<Value, Value> map, long key) {
try {
return map.lock(ValueLong.get(key));
} catch (IllegalStateException ex) {
throw mvTable.convertException(ex);
}
}
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
ValueLong min = extractPKFromRow(first, ValueLong.MIN);
ValueLong max = extractPKFromRow(last, ValueLong.MAX);
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(min, max));
}
private ValueLong (SearchRow row, ValueLong defaultValue) {
ValueLong result;
if (row == null) {
result = defaultValue;
} else if (mainIndexColumn == SearchRow.ROWID_INDEX) {
result = ValueLong.get(row.getKey());
} else {
ValueLong v = (ValueLong) row.getValue(mainIndexColumn);
if (v == null) {
result = ValueLong.get(row.getKey());
} else {
result = v;
}
}
return result;
}
@Override
public MVTable getTable() {
return mvTable;
}
@Override
public Row getRow(Session session, long key) {
TransactionMap<Value, Value> map = getMap(session);
Value v = map.get(ValueLong.get(key));
if (v == null) {
throw DbException.get(ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX,
getSQL(false), String.valueOf(key));
}
return getRow(session, key, (ValueArray) v);
}
private static Row getRow(Session session, long key, ValueArray array) {
Row row = session.createRow(array.getList(), 0);
row.setKey(key);
return row;
}
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder,
AllColumnsForPlan allColumnsSet) {
try {
return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
filters, filter, sortOrder, true, allColumnsSet);
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
}
}
@Override
public int getColumnIndex(Column col) {
return SearchRow.ROWID_INDEX;
}
@Override
public boolean isFirstColumn(Column column) {
return false;
}
@Override
public void remove(Session session) {
TransactionMap<Value, Value> map = getMap(session);
if (!map.isClosed()) {
Transaction t = session.getTransaction();
t.removeMap(map);
}
}
@Override
public void truncate(Session session) {
TransactionMap<Value, Value> map = getMap(session);
if (mvTable.getContainsLargeObject()) {
database.getLobStorage().removeAllForTable(table.getId());
}
map.clear();
}
@Override
public boolean canGetFirstOrLast() {
return true;
}
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
TransactionMap<Value, Value> map = getMap(session);
ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) {
return new MVStoreCursor(session,
Collections.<Entry<Value, Value>> emptyIterator());
}
Value value = map.get(v);
Entry<Value, Value> e = new AbstractMap.SimpleImmutableEntry<Value, Value>(v, value);
List<Entry<Value, Value>> list = Collections.singletonList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator());
c.next();
return c;
}
@Override
public boolean needRebuild() {
return false;
}
@Override
public long getRowCount(Session session) {
TransactionMap<Value, Value> map = getMap(session);
return map.sizeAsLong();
}
public long getRowCountMax() {
return dataMap.sizeAsLongMax();
}
@Override
public long getRowCountApproximation() {
return getRowCountMax();
}
@Override
public long getDiskSpaceUsed() {
return dataMap.map.getRootPage().getDiskSpaceUsed();
}
public String getMapName() {
return mapName;
}
@Override
public void checkRename() {
}
ValueLong getKey(SearchRow row, ValueLong ifEmpty, ValueLong ifNull) {
if (row == null) {
return ifEmpty;
}
Value v = row.getValue(mainIndexColumn);
if (v == null) {
throw DbException.throwInternalError(row.toString());
} else if (v == ValueNull.INSTANCE) {
return ifNull;
}
return (ValueLong) v.convertTo(Value.LONG);
}
Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(first, last));
}
@Override
public boolean isRowIdIndex() {
return true;
}
TransactionMap<Value, Value> getMap(Session session) {
if (session == null) {
return dataMap;
}
Transaction t = session.getTransaction();
return dataMap.getInstance(t);
}
static class MVStoreCursor implements Cursor {
private final Session session;
private final Iterator<Entry<Value, Value>> it;
private Entry<Value, Value> current;
private Row row;
public MVStoreCursor(Session session, Iterator<Entry<Value, Value>> it) {
this.session = session;
this.it = it;
}
@Override
public Row get() {
if (row == null) {
if (current != null) {
ValueArray array = (ValueArray) current.getValue();
row = session.createRow(array.getList(), 0);
row.setKey(current.getKey().getLong());
}
}
return row;
}
@Override
public SearchRow getSearchRow() {
return get();
}
@Override
public boolean next() {
current = it.hasNext() ? it.next() : null;
row = null;
return current != null;
}
@Override
public boolean previous() {
throw DbException.getUnsupportedException("previous");
}
}
}