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

MVStore: support statements (WIP)

上级 f4d347df
......@@ -105,6 +105,8 @@ public class Session extends SessionWithState {
private final int queryCacheSize;
private SmallLRUCache<String, Command> queryCache;
private Transaction transaction;
private long startStatement = -1;
private long statementVersion;
public Session(Database database, User user, int id) {
this.database = database;
......@@ -446,6 +448,10 @@ public class Session extends SessionWithState {
checkCommitRollback();
currentTransactionName = null;
transactionStart = 0;
if (transaction != null) {
transaction.commit();
transaction = null;
}
if (containsUncommitted()) {
// need to commit even if rollback is not possible
// (create/drop table and so on)
......@@ -503,6 +509,10 @@ public class Session extends SessionWithState {
*/
public void rollback() {
checkCommitRollback();
if (transaction != null) {
transaction.rollback();
transaction = null;
}
currentTransactionName = null;
boolean needCommit = false;
if (undoLog.size() > 0) {
......@@ -592,6 +602,9 @@ public class Session extends SessionWithState {
* @param row the row
*/
public void log(Table table, short operation, Row row) {
if (table.isMVStore()) {
return;
}
if (undoLogEnabled) {
UndoLogRecord log = new UndoLogRecord(table, operation, row);
// called _after_ the row was inserted successfully into the table,
......@@ -1248,8 +1261,17 @@ public class Session extends SessionWithState {
public Transaction getTransaction(TransactionStore store) {
if (transaction == null) {
transaction = store.begin();
statementVersion = -1;
}
return transaction;
}
public long getStatementVersion() {
if (startStatement == -1) {
startStatement = transaction.setSavepoint();
statementVersion = transaction.getCurrentVersion();
}
return statementVersion;
}
}
......@@ -892,6 +892,10 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
int i = searchRoot(oldest);
if (i < 0) {
i = -i - 1;
}
i--;
if (i <= 0) {
return;
}
// create a new instance
......@@ -1019,7 +1023,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
// need to copy because it can change
Page r = root;
if (version >= r.getVersion() &&
(r.getVersion() >= 0 ||
(version == store.getCurrentVersion() ||
r.getVersion() >= 0 ||
version <= createVersion ||
store.getFile() == null)) {
newest = r;
......@@ -1183,6 +1188,14 @@ public class MVMap<K, V> extends AbstractMap<K, V>
this.keyType = keyType;
return this;
}
public DataType getKeyType() {
return keyType;
}
public DataType getValueType() {
return valueType;
}
/**
* Set the key data type.
......
......@@ -776,6 +776,9 @@ public class MVStore {
if (!hasUnsavedChanges()) {
return currentVersion;
}
if (readOnly) {
throw DataUtils.newIllegalStateException("This store is read-only");
}
int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentStoreVersion = currentVersion;
long version = incrementVersion();
......@@ -938,6 +941,15 @@ public class MVStore {
writeFileHeader();
shrinkFileIfPossible(1);
}
for (MVMap<?, ?> m : changed) {
Page p = m.getRoot();
if (p.getTotalCount() > 0) {
p.writeEnd();
}
}
meta.getRoot().writeEnd();
// some pages might have been changed in the meantime (in the newest version)
unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount);
currentStoreVersion = -1;
......
......@@ -838,12 +838,27 @@ public class Page {
if (p != null) {
buff = p.writeUnsavedRecursive(chunk, buff);
children[i] = p.getPos();
childrenPages[i] = null;
}
}
}
return write(chunk, buff);
}
/**
* Unlink the children recursively after all data is written.
*/
void writeEnd() {
if (!isLeaf()) {
int len = children.length;
for (int i = 0; i < len; i++) {
Page p = childrenPages[i];
if (p != null) {
p.writeEnd();
childrenPages[i] = null;
}
}
}
}
long getVersion() {
return version;
......
......@@ -6,12 +6,16 @@
*/
package org.h2.mvstore;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.h2.mvstore.MVMap.Builder;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
import org.h2.util.New;
/**
......@@ -64,14 +68,29 @@ public class TransactionStore {
* @param store the store
*/
public TransactionStore(MVStore store) {
this(store, new ObjectDataType());
}
/**
* Create a new transaction store.
*
* @param store the store
* @param keyType the data type for map keys
*/
public TransactionStore(MVStore store, DataType keyType) {
this.store = store;
settings = store.openMap("settings");
openTransactions = store.openMap("openTransactions",
new MVMapConcurrent.Builder<Long, Object[]>());
// commit could be faster if we have one undo log per transaction,
// or a range delete operation for maps
undoLog = store.openMap("undoLog",
new MVMapConcurrent.Builder<long[], Object[]>());
ArrayType valueType = new ArrayType(new DataType[]{
new ObjectDataType(), new ObjectDataType(), keyType
});
MVMapConcurrent.Builder<long[], Object[]> builder =
new MVMapConcurrent.Builder<long[], Object[]>().
valueType(valueType);
undoLog = store.openMap("undoLog", builder);
init();
}
......@@ -103,8 +122,8 @@ public class TransactionStore {
}
/**
* Get the list of currently open transactions.
*
* Get the list of currently open transactions that have pending writes.
*
* @return the list of transactions
*/
public synchronized List<Transaction> getOpenTransactions() {
......@@ -130,16 +149,22 @@ public class TransactionStore {
public synchronized Transaction begin() {
store.incrementVersion();
long transactionId = lastTransactionId++;
int status = Transaction.STATUS_OPEN;
return new Transaction(this, transactionId, status, null, 0);
}
void storeTransaction(Transaction t) {
long transactionId = t.getId();
if (openTransactions.containsKey(transactionId)) {
return;
}
Object[] v = { t.getStatus(), null };
openTransactions.put(transactionId, v);
openTransactionMap.put(transactionId, t);
if (lastTransactionId > lastTransactionIdStored) {
lastTransactionIdStored += 32;
settings.put(LAST_TRANSACTION_ID, "" + lastTransactionIdStored);
}
int status = Transaction.STATUS_OPEN;
Object[] v = { status, null };
openTransactions.put(transactionId, v);
Transaction t = new Transaction(this, transactionId, status, null, 0);
openTransactionMap.put(transactionId, t);
return t;
}
/**
......@@ -147,37 +172,56 @@ public class TransactionStore {
*
* @param transactionId the transaction id
*/
void prepare(long transactionId) {
Object[] old = openTransactions.get(transactionId);
void prepare(Transaction t) {
storeTransaction(t);
Object[] old = openTransactions.get(t.getId());
Object[] v = { Transaction.STATUS_PREPARED, old[1] };
openTransactions.put(transactionId, v);
openTransactions.put(t.getId(), v);
store.commit();
}
/**
* Log an entry.
*
* @param t the transaction
* @param logId the log id
* @param opType the operation type
* @param mapId the map id
* @param key the key
*/
void log(Transaction t, long logId, int opType, int mapId,
Object key) {
storeTransaction(t);
long[] undoKey = { t.getId(), logId };
Object[] log = new Object[] { opType, mapId, key };
undoLog.put(undoKey, log);
}
/**
* Set the name of a transaction.
*
* @param transactionId the transaction id
* @param t the transaction
* @param name the new name
*/
void setTransactionName(long transactionId, String name) {
Object[] old = openTransactions.get(transactionId);
void setTransactionName(Transaction t, String name) {
storeTransaction(t);
Object[] old = openTransactions.get(t.getId());
Object[] v = { old[0], name };
openTransactions.put(transactionId, v);
openTransactions.put(t.getId(), v);
store.commit();
}
/**
* Commit a transaction.
*
* @param transactionId the transaction id
* @param t the transaction
* @param maxLogId the last log id
*/
void commit(long transactionId, long maxLogId) {
void commit(Transaction t, long maxLogId) {
store.incrementVersion();
for (long logId = 0; logId < maxLogId; logId++) {
Object[] op = undoLog.get(new long[] {
transactionId, logId });
t.getId(), logId });
int opType = (Integer) op[0];
if (opType == Transaction.OP_REMOVE) {
int mapId = (Integer) op[1];
......@@ -196,36 +240,44 @@ public class TransactionStore {
}
undoLog.remove(logId);
}
openTransactions.remove(transactionId);
openTransactionMap.remove(transactionId);
openTransactions.remove(t.getId());
openTransactionMap.remove(t.getId());
store.commit();
long oldestVersion = store.getCurrentVersion();
for (Transaction u : openTransactionMap.values()) {
long v = u.startVersion;
if (v < oldestVersion) {
oldestVersion = v;
}
}
store.setRetainVersion(oldestVersion);
}
/**
* Roll a transaction back.
*
* @param transactionId the transaction id
* @param t the transaction
* @param maxLogId the last log id
*/
void rollback(long transactionId, long maxLogId) {
rollbackTo(transactionId, maxLogId, 0);
openTransactions.remove(transactionId);
openTransactionMap.remove(transactionId);
void rollback(Transaction t, long maxLogId) {
rollbackTo(t, maxLogId, 0);
openTransactions.remove(t.getId());
openTransactionMap.remove(t.getId());
store.commit();
}
/**
* Rollback to an old savepoint.
*
* @param transactionId the transaction id
* @param t the transaction
* @param maxLogId the last log id
* @param toLogId the log id to roll back to
*/
void rollbackTo(long transactionId, long maxLogId, long toLogId) {
void rollbackTo(Transaction t, long maxLogId, long toLogId) {
store.incrementVersion();
for (long logId = maxLogId - 1; logId >= toLogId; logId--) {
Object[] op = undoLog.get(new long[] {
transactionId, logId });
t.getId(), logId });
int mapId = ((Integer) op[1]).intValue();
Map<String, String> meta = store.getMetaMap();
String m = meta.get("map." + mapId);
......@@ -286,6 +338,11 @@ public class TransactionStore {
* The transaction store.
*/
final TransactionStore store;
/**
* The version of the store at the time the transaction was started.
*/
final long startVersion;
/**
* The transaction id.
......@@ -300,6 +357,7 @@ public class TransactionStore {
Transaction(TransactionStore store, long transactionId, int status, String name, long logId) {
this.store = store;
this.startVersion = store.store.getCurrentVersion();
this.transactionId = transactionId;
this.status = status;
this.name = name;
......@@ -331,7 +389,7 @@ public class TransactionStore {
*/
public void setName(String name) {
checkOpen();
store.setTransactionName(transactionId, name);
store.setTransactionName(this, name);
this.name = name;
}
......@@ -363,9 +421,7 @@ public class TransactionStore {
* @param key the key
*/
void log(int opType, int mapId, Object key) {
long[] undoKey = { transactionId, logId++ };
Object[] log = new Object[] { opType, mapId, key };
store.undoLog.put(undoKey, log);
store.log(this, logId++, opType, mapId, key);
}
/**
......@@ -377,8 +433,7 @@ public class TransactionStore {
* @return the transaction map
*/
public <K, V> TransactionMap<K, V> openMap(String name) {
checkOpen();
return new TransactionMap<K, V>(this, name, -1);
return openMap(name, -1);
}
/**
......@@ -392,7 +447,31 @@ public class TransactionStore {
*/
public <K, V> TransactionMap<K, V> openMap(String name, long readVersion) {
checkOpen();
return new TransactionMap<K, V>(this, name, readVersion);
return new TransactionMap<K, V>(this, name, new ObjectDataType(),
new ObjectDataType(), readVersion);
}
/**
* Open the map to store the data.
*
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param readVersion the version used for reading
* @param builder the builder
* @return the transaction map
*/
public <K, V> TransactionMap<K, V> openMap(String name, long readVersion, Builder<K, V> builder) {
checkOpen();
DataType keyType = builder.getKeyType();
if (keyType == null) {
keyType = new ObjectDataType();
}
DataType valueType = builder.getValueType();
if (valueType == null) {
valueType = new ObjectDataType();
}
return new TransactionMap<K, V>(this, name, keyType, valueType, readVersion);
}
/**
......@@ -403,7 +482,7 @@ public class TransactionStore {
*/
public void rollbackToSavepoint(long savepointId) {
checkOpen();
store.rollbackTo(transactionId, this.logId, savepointId);
store.rollbackTo(this, this.logId, savepointId);
this.logId = savepointId;
}
......@@ -413,7 +492,7 @@ public class TransactionStore {
*/
public void prepare() {
checkOpen();
store.prepare(transactionId);
store.prepare(this);
status = STATUS_PREPARED;
}
......@@ -422,7 +501,7 @@ public class TransactionStore {
*/
public void commit() {
if (status != STATUS_CLOSED) {
store.commit(transactionId, logId);
store.commit(this, logId);
status = STATUS_CLOSED;
}
}
......@@ -432,7 +511,7 @@ public class TransactionStore {
*/
public void rollback() {
if (status != STATUS_CLOSED) {
store.rollback(transactionId, logId);
store.rollback(this, logId);
status = STATUS_CLOSED;
}
}
......@@ -446,6 +525,10 @@ public class TransactionStore {
}
}
public long getCurrentVersion() {
return store.store.getCurrentVersion();
}
}
/**
......@@ -479,9 +562,15 @@ public class TransactionStore {
*/
private final MVMap<K, Object[]> mapRead;
TransactionMap(Transaction transaction, String name, long readVersion) {
TransactionMap(Transaction transaction, String name, DataType keyType,
DataType valueType, long readVersion) {
this.transaction = transaction;
mapWrite = transaction.store.store.openMap(name);
ArrayType arrayType = new ArrayType(new DataType[] {
new ObjectDataType(), new ObjectDataType(), valueType
});
MVMap.Builder<K, Object[]> builder = new MVMap.Builder<K, Object[]>()
.keyType(keyType).valueType(arrayType);
mapWrite = transaction.store.store.openMap(name, builder);
mapId = mapWrite.getId();
if (readVersion >= 0) {
mapRead = mapWrite.openVersion(readVersion);
......@@ -715,6 +804,10 @@ public class TransactionStore {
public V getLatest(K key) {
return get(key, mapWrite);
}
public boolean containsKey(K key) {
return get(key) != null;
}
/**
* Get the value for the given key.
......@@ -754,20 +847,123 @@ public class TransactionStore {
m = mapWrite.openVersion(oldVersion);
}
}
}
public void renameMap(String newMapName) {
// TODO rename maps transactionally
mapWrite.renameMap(newMapName);
}
public boolean isClosed() {
return mapWrite.isClosed();
}
public void removeMap() {
mapWrite.removeMap();
}
public void clear() {
// TODO truncate transactionally
mapWrite.clear();
}
public K firstKey() {
// TODO transactional firstKey
return mapRead.firstKey();
}
public K lastKey() {
// TODO transactional lastKey
return mapRead.lastKey();
}
public Iterator<K> keyIterator(K from) {
// TODO transactional keyIterator
return mapRead.keyIterator(from);
}
public K ceilingKey(K key) {
// TODO transactional ceilingKey
return mapRead.ceilingKey(key);
}
public K higherKey(K key) {
// TODO transactional higherKey
return mapRead.higherKey(key);
}
public K lowerKey(K key) {
// TODO Auto-generated method stub
return mapRead.lowerKey(key);
}
public Transaction getTransaction() {
return transaction;
}
}
/**
* Open the map to store the data.
*
* @param <A> the key type
* @param <B> the value type
* @param name the map name
* @param builder the builder
* @return the map
* A data type that contains an array of objects with the specified data
* types.
*/
public <A, B> MVMap<A, B> openMap(String name, Builder<A, B> builder) {
int todo;
return store.openMap(name, builder);
public static class ArrayType implements DataType {
private final int arrayLength;
private final DataType[] elementTypes;
ArrayType(DataType[] elementTypes) {
this.arrayLength = elementTypes.length;
this.elementTypes = elementTypes;
}
@Override
public int getMemory(Object obj) {
Object[] array = (Object[]) obj;
int size = 0;
for (int i = 0; i < arrayLength; i++) {
DataType t = elementTypes[i];
size += t.getMemory(array[i]);
}
return size;
}
@Override
public int compare(Object aObj, Object bObj) {
if (aObj == bObj) {
return 0;
}
Object[] a = (Object[]) aObj;
Object[] b = (Object[]) bObj;
for (int i = 0; i < arrayLength; i++) {
DataType t = elementTypes[i];
int comp = t.compare(a[i], b[i]);
if (comp != 0) {
return comp;
}
}
return 0;
}
@Override
public ByteBuffer write(ByteBuffer buff, Object obj) {
Object[] array = (Object[]) obj;
for (int i = 0; i < arrayLength; i++) {
DataType t = elementTypes[i];
t.write(buff, array[i]);
}
return buff;
}
@Override
public Object read(ByteBuffer buff) {
Object[] array = new Object[arrayLength];
for (int i = 0; i < arrayLength; i++) {
DataType t = elementTypes[i];
array[i] = t.read(buff);
}
return array;
}
}
}
......
......@@ -18,12 +18,16 @@ import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.TransactionStore.Transaction;
import org.h2.mvstore.TransactionStore.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.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
/**
......@@ -32,7 +36,8 @@ import org.h2.value.ValueNull;
public class MVPrimaryIndex extends BaseIndex {
private final MVTable mvTable;
private MVMap<Long, Value[]> map;
private String mapName;
private MVMap.Builder<Value, Value> mapBuilder;
private long lastKey;
private int mainIndexColumn = -1;
......@@ -44,12 +49,18 @@ public class MVPrimaryIndex extends BaseIndex {
for (int i = 0; i < columns.length; i++) {
sortTypes[i] = SortOrder.ASCENDING;
}
ValueArrayDataType t = new ValueArrayDataType(
ValueDataType keyType = new ValueDataType(
null, null, null);
ValueDataType valueType = new ValueDataType(
db.getCompareMode(), db, sortTypes);
map = table.getStore().openMap(getName() + "_" + getId(),
new MVMap.Builder<Long, Value[]>().valueType(t));
Long k = map.lastKey();
lastKey = k == null ? 0 : k;
mapName = getName() + "_" + getId();
mapBuilder = new MVMap.Builder<Value, Value>().
keyType(keyType).
valueType(valueType);
TransactionMap<Value, Value> map = getMap(null);
Value k = map.lastKey();
map.getTransaction().commit();
lastKey = k == null ? 0 : k.getLong();
}
/**
......@@ -58,9 +69,12 @@ public class MVPrimaryIndex extends BaseIndex {
* @param newName the new name
*/
public void renameTable(String newName) {
MVMap<Long, Value[]> map = getMap(null);
TransactionMap<Value, Value> map = getMap(null);
rename(newName + "_DATA");
map.renameMap(newName + "_DATA_" + getId());
String newMapName = newName + "_DATA_" + getId();
map.renameMap(newMapName);
map.getTransaction().commit();
mapName = newMapName;
}
public String getCreateSQL() {
......@@ -98,8 +112,8 @@ public class MVPrimaryIndex extends BaseIndex {
for (int i = 0; i < array.length; i++) {
array[i] = row.getValue(i);
}
MVMap<Long, Value[]> map = getMap(session);
if (map.containsKey(row.getKey())) {
TransactionMap<Value, Value> map = getMap(session);
if (map.containsKey(ValueLong.get(row.getKey()))) {
String sql = "PRIMARY KEY ON " + table.getSQL();
if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) {
sql += "(" + indexColumns[mainIndexColumn].getSQL() + ")";
......@@ -108,14 +122,14 @@ public class MVPrimaryIndex extends BaseIndex {
e.setSource(this);
throw e;
}
map.put(row.getKey(), array);
map.put(ValueLong.get(row.getKey()), ValueArray.get(array));
lastKey = Math.max(lastKey, row.getKey());
}
@Override
public void remove(Session session, Row row) {
MVMap<Long, Value[]> map = getMap(session);
Value[] old = map.remove(row.getKey());
TransactionMap<Value, Value> map = getMap(session);
Value old = map.remove(ValueLong.get(row.getKey()));
if (old == null) {
throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1,
getSQL() + ": " + row.getKey());
......@@ -145,8 +159,9 @@ public class MVPrimaryIndex extends BaseIndex {
max = v.getLong();
}
}
MVMap<Long, Value[]> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(min), max);
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(
ValueLong.get(min)), max);
}
public MVTable getTable() {
......@@ -154,16 +169,16 @@ public class MVPrimaryIndex extends BaseIndex {
}
public Row getRow(Session session, long key) {
MVMap<Long, Value[]> map = getMap(session);
Value[] array = map.get(key);
Row row = new Row(array, 0);
TransactionMap<Value, Value> map = getMap(session);
ValueArray array = (ValueArray) map.get(ValueLong.get(key));
Row row = new Row(array.getList(), 0);
row.setKey(key);
return row;
}
@Override
public double getCost(Session session, int[] masks) {
MVMap<Long, Value[]> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
long cost = 10 * (map.getSize() + Constants.COST_ROW_OFFSET);
return cost;
}
......@@ -176,7 +191,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public void remove(Session session) {
MVMap<Long, Value[]> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
if (!map.isClosed()) {
map.removeMap();
}
......@@ -184,7 +199,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public void truncate(Session session) {
MVMap<Long, Value[]> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
if (mvTable.getContainsLargeObject()) {
database.getLobStorage().removeAllForTable(table.getId());
}
......@@ -198,12 +213,13 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
MVMap<Long, Value[]> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
if (map.getSize() == 0) {
return new MVStoreCursor(session, Collections.<Long>emptyList().iterator(), 0);
return new MVStoreCursor(session, Collections.<Value>emptyList().iterator(), 0);
}
long key = first ? map.firstKey() : map.lastKey();
MVStoreCursor cursor = new MVStoreCursor(session, Arrays.asList(key).iterator(), key);
long key = first ? map.firstKey().getLong() : map.lastKey().getLong();
MVStoreCursor cursor = new MVStoreCursor(session,
Arrays.asList((Value) ValueLong.get(key)).iterator(), key);
cursor.next();
return cursor;
}
......@@ -215,14 +231,16 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public long getRowCount(Session session) {
MVMap<Long, Value[]> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
return map.getSize();
}
@Override
public long getRowCountApproximation() {
MVMap<Long, Value[]> map = getMap(null);
return map.getSize();
TransactionMap<Value, Value> map = getMap(null);
long size = map.getSize();
map.getTransaction().commit();
return size;
}
public long getDiskSpaceUsed() {
......@@ -265,8 +283,8 @@ public class MVPrimaryIndex extends BaseIndex {
* @return the cursor
*/
Cursor find(Session session, long first, long last) {
MVMap<Long, Value[]> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(first), last);
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(ValueLong.get(first)), last);
}
/**
......@@ -275,12 +293,12 @@ public class MVPrimaryIndex extends BaseIndex {
class MVStoreCursor implements Cursor {
private final Session session;
private final Iterator<Long> it;
private final Iterator<Value> it;
private final long last;
private Long current;
private ValueLong current;
private Row row;
public MVStoreCursor(Session session, Iterator<Long> it, long last) {
public MVStoreCursor(Session session, Iterator<Value> it, long last) {
this.session = session;
this.it = it;
this.last = last;
......@@ -290,7 +308,7 @@ public class MVPrimaryIndex extends BaseIndex {
public Row get() {
if (row == null) {
if (current != null) {
row = getRow(session, current);
row = getRow(session, current.getLong());
}
}
return row;
......@@ -303,8 +321,8 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public boolean next() {
current = it.next();
if (current != null && current > last) {
current = (ValueLong) it.next();
if (current != null && current.getLong() > last) {
current = null;
}
row = null;
......@@ -329,9 +347,13 @@ public class MVPrimaryIndex extends BaseIndex {
* @param session the session
* @return the map
*/
MVMap<Long, Value[]> getMap(Session session) {
// return mvTable.getTransaction(session).openMap(name)
return map;
TransactionMap<Value, Value> getMap(Session session) {
if (session == null) {
return mvTable.getTransaction(null).openMap(mapName, -1, mapBuilder);
}
Transaction t = mvTable.getTransaction(session);
long version = session.getStatementVersion();
return t.openMap(mapName, version, mapBuilder);
}
}
......@@ -17,6 +17,8 @@ import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.TransactionStore.Transaction;
import org.h2.mvstore.TransactionStore.TransactionMap;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
......@@ -24,6 +26,7 @@ import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.util.New;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
......@@ -38,7 +41,8 @@ public class MVSecondaryIndex extends BaseIndex {
final MVTable mvTable;
private final int keyColumns;
private MVMap<Value[], Long> map2;
private String mapName;
private MVMap.Builder<Value, Value> mapBuilder;
public MVSecondaryIndex(Database db, MVTable table, int id, String indexName,
IndexColumn[] columns, IndexType indexType) {
......@@ -55,12 +59,13 @@ public class MVSecondaryIndex extends BaseIndex {
sortTypes[i] = columns[i].sortType;
}
sortTypes[keyColumns - 1] = SortOrder.ASCENDING;
String name = getName() + "_" + getId();
ValueArrayDataType t = new ValueArrayDataType(
mapName = getName() + "_" + getId();
ValueDataType keyType = new ValueDataType(
db.getCompareMode(), db, sortTypes);
map2 = table.getStore().openMap(name,
new MVMap.Builder<Value[], Long>().keyType(t));
ValueDataType valueType = new ValueDataType(null, null, null);
mapBuilder = new MVMap.Builder<Value, Value>().
keyType(keyType).
valueType(valueType);
}
private static void checkIndexColumnTypes(IndexColumn[] columns) {
......@@ -78,20 +83,23 @@ public class MVSecondaryIndex extends BaseIndex {
}
public void rename(String newName) {
MVMap<Value[], Long> map = getMap(null);
map.renameMap(newName + "_" + getId());
TransactionMap<Value, Value> map = getMap(null);
String newMapName = newName + "_" + getId();
map.renameMap(newMapName);
map.getTransaction().commit();
mapName = newMapName;
super.rename(newName);
}
@Override
public void add(Session session, Row row) {
MVMap<Value[], Long> map = getMap(session);
Value[] array = getKey(row);
TransactionMap<Value, Value> map = getMap(session);
ValueArray array = getKey(row);
if (indexType.isUnique()) {
array[keyColumns - 1] = ValueLong.get(0);
Value[] key = map.ceilingKey(array);
array.getList()[keyColumns - 1] = ValueLong.get(0);
ValueArray key = (ValueArray) map.ceilingKey(array);
if (key != null) {
SearchRow r2 = getRow(key);
SearchRow r2 = getRow(key.getList());
if (compareRows(row, r2) == 0) {
if (!containsNullAndAllowMultipleNull(r2)) {
throw getDuplicateKeyException();
......@@ -99,15 +107,15 @@ public class MVSecondaryIndex extends BaseIndex {
}
}
}
array[keyColumns - 1] = ValueLong.get(row.getKey());
map.put(array, Long.valueOf(0));
array.getList()[keyColumns - 1] = ValueLong.get(row.getKey());
map.put(array, ValueLong.get(0));
}
@Override
public void remove(Session session, Row row) {
Value[] array = getKey(row);
MVMap<Value[], Long> map = getMap(session);
Long old = map.remove(array);
ValueArray array = getKey(row);
TransactionMap<Value, Value> map = getMap(session);
Value old = map.remove(array);
if (old == null) {
if (old == null) {
throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1,
......@@ -118,12 +126,12 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
Value[] min = getKey(first);
MVMap<Value[], Long> map = getMap(session);
Value min = getKey(first);
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(min), last);
}
private Value[] getKey(SearchRow r) {
private ValueArray getKey(SearchRow r) {
if (r == null) {
return null;
}
......@@ -136,7 +144,7 @@ public class MVSecondaryIndex extends BaseIndex {
}
}
array[keyColumns - 1] = ValueLong.get(r.getKey());
return array;
return ValueArray.get(array);
}
/**
......@@ -164,13 +172,13 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public double getCost(Session session, int[] masks) {
MVMap<Value[], Long> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
return 10 * getCostRangeIndex(masks, map.getSize());
}
@Override
public void remove(Session session) {
MVMap<Value[], Long> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
if (!map.isClosed()) {
map.removeMap();
}
......@@ -178,7 +186,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public void truncate(Session session) {
MVMap<Value[], Long> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
map.clear();
}
......@@ -189,18 +197,18 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
MVMap<Value[], Long> map = getMap(session);
Value[] key = first ? map.firstKey() : map.lastKey();
TransactionMap<Value, Value> map = getMap(session);
Value key = first ? map.firstKey() : map.lastKey();
while (true) {
if (key == null) {
return new MVStoreCursor(session, Collections.<Value[]>emptyList().iterator(), null);
return new MVStoreCursor(session, Collections.<Value>emptyList().iterator(), null);
}
if (key[0] != ValueNull.INSTANCE) {
if (((ValueArray) key).getList()[0] != ValueNull.INSTANCE) {
break;
}
key = first ? map.higherKey(key) : map.lowerKey(key);
}
ArrayList<Value[]> list = New.arrayList();
ArrayList<Value> list = New.arrayList();
list.add(key);
MVStoreCursor cursor = new MVStoreCursor(session, list.iterator(), null);
cursor.next();
......@@ -209,20 +217,24 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public boolean needRebuild() {
MVMap<Value[], Long> map = getMap(null);
return map.getSize() == 0;
TransactionMap<Value, Value> map = getMap(null);
boolean result = map.getSize() == 0;
map.getTransaction().commit();
return result;
}
@Override
public long getRowCount(Session session) {
MVMap<Value[], Long> map = getMap(session);
TransactionMap<Value, Value> map = getMap(session);
return map.getSize();
}
@Override
public long getRowCountApproximation() {
MVMap<Value[], Long> map = getMap(null);
return map.getSize();
TransactionMap<Value, Value> map = getMap(null);
long size = map.getSize();
map.getTransaction().commit();
return size;
}
public long getDiskSpaceUsed() {
......@@ -241,13 +253,13 @@ public class MVSecondaryIndex extends BaseIndex {
class MVStoreCursor implements Cursor {
private final Session session;
private final Iterator<Value[]> it;
private final Iterator<Value> it;
private final SearchRow last;
private Value[] current;
private Value current;
private SearchRow searchRow;
private Row row;
public MVStoreCursor(Session session, Iterator<Value[]> it, SearchRow last) {
public MVStoreCursor(Session session, Iterator<Value> it, SearchRow last) {
this.session = session;
this.it = it;
this.last = last;
......@@ -268,7 +280,7 @@ public class MVSecondaryIndex extends BaseIndex {
public SearchRow getSearchRow() {
if (searchRow == null) {
if (current != null) {
searchRow = getRow(current);
searchRow = getRow(((ValueArray) current).getList());
}
}
return searchRow;
......@@ -302,8 +314,13 @@ public class MVSecondaryIndex extends BaseIndex {
* @param session the session
* @return the map
*/
MVMap<Value[], Long> getMap(Session session) {
return map2;
TransactionMap<Value, Value> getMap(Session session) {
if (session == null) {
return mvTable.getTransaction(null).openMap(mapName, -1, mapBuilder);
}
Transaction t = mvTable.getTransaction(session);
long version = session.getStatementVersion();
return t.openMap(mapName, version, mapBuilder);
}
}
......@@ -680,6 +680,10 @@ public class MVTable extends TableBase {
* @return the transaction
*/
Transaction getTransaction(Session session) {
if (session == null) {
// TODO need to commit/rollback the transaction
return store.begin();
}
return session.getTransaction(store);
}
......@@ -698,5 +702,9 @@ public class MVTable extends TableBase {
public String toString() {
return getSQL();
}
public boolean isMVStore() {
return true;
}
}
......@@ -148,7 +148,8 @@ public class MVTableEngine implements TableEngine {
public Store(Database db, MVStore store) {
this.db = db;
this.store = store;
this.transactionStore = new TransactionStore(store);
this.transactionStore = new TransactionStore(store,
new ValueDataType(null, null, null));
}
public MVStore getStore() {
......
/*
* Copyright 2004-2013 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.mvstore.db;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.type.DataType;
import org.h2.result.SortOrder;
import org.h2.store.Data;
import org.h2.store.DataHandler;
import org.h2.store.LobStorage;
import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueByte;
import org.h2.value.ValueBytes;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueInt;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLob;
import org.h2.value.ValueLobDb;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;
import org.h2.value.ValueShort;
import org.h2.value.ValueString;
import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueUuid;
/**
* A row type.
*/
public class ValueDataType implements DataType {
static final String PREFIX = ValueDataType.class.getName();
private static final int INT_0_15 = 32;
private static final int LONG_0_7 = 48;
private static final int DECIMAL_0_1 = 56;
private static final int DECIMAL_SMALL_0 = 58;
private static final int DECIMAL_SMALL = 59;
private static final int DOUBLE_0_1 = 60;
private static final int FLOAT_0_1 = 62;
private static final int BOOLEAN_FALSE = 64;
private static final int BOOLEAN_TRUE = 65;
private static final int INT_NEG = 66;
private static final int LONG_NEG = 67;
private static final int STRING_0_31 = 68;
private static final int BYTES_0_31 = 100;
private static final int LOCAL_TIME = 132;
private static final int LOCAL_DATE = 133;
private static final int LOCAL_TIMESTAMP = 134;
private static final long MILLIS_PER_MINUTE = 1000 * 60;
final DataHandler handler;
final CompareMode compareMode;
final int[] sortTypes;
ValueDataType(CompareMode compareMode, DataHandler handler, int[] sortTypes) {
this.compareMode = compareMode;
this.handler = handler;
this.sortTypes = sortTypes;
}
public int compare(Object a, Object b) {
if (a == b) {
return 0;
}
if (a instanceof ValueArray && b instanceof ValueArray) {
Value[] ax = ((ValueArray) a).getList();
Value[] bx = ((ValueArray) b).getList();
int al = ax.length;
int bl = bx.length;
int len = Math.min(al, bl);
for (int i = 0; i < len; i++) {
int comp = compareValues(ax[i], bx[i], sortTypes[i]);
if (comp != 0) {
return comp;
}
}
if (len < al) {
return -1;
} else if (len < bl) {
return 1;
}
return 0;
}
return compareValues((Value) a, (Value) b, SortOrder.ASCENDING);
}
private int compareValues(Value a, Value b, int sortType) {
if (a == b) {
return 0;
}
boolean aNull = a == null, bNull = b == null;
if (aNull || bNull) {
return SortOrder.compareNull(aNull, sortType);
}
int comp = compareTypeSave(a, b);
if ((sortType & SortOrder.DESCENDING) != 0) {
comp = -comp;
}
return comp;
}
private int compareTypeSave(Value a, Value b) {
if (a == b) {
return 0;
}
int dataType = Value.getHigherOrder(a.getType(), b.getType());
a = a.convertTo(dataType);
b = b.convertTo(dataType);
return a.compareTypeSave(b, compareMode);
}
public int getMemory(Object obj) {
return getMemory((Value) obj);
}
private static int getMemory(Value v) {
return v == null ? 0 : v.getMemory();
}
public Value read(ByteBuffer buff) {
return readValue(buff);
}
public ByteBuffer write(ByteBuffer buff, Object obj) {
Value x = (Value) obj;
buff = DataUtils.ensureCapacity(buff, 0);
buff = writeValue(buff, x);
return buff;
}
private ByteBuffer writeValue(ByteBuffer buff, Value v) {
int start = buff.position();
if (v == ValueNull.INSTANCE) {
buff.put((byte) 0);
return buff;
}
int type = v.getType();
switch (type) {
case Value.BOOLEAN:
buff.put((byte) (v.getBoolean().booleanValue() ? BOOLEAN_TRUE : BOOLEAN_FALSE));
break;
case Value.BYTE:
buff.put((byte) type);
buff.put(v.getByte());
break;
case Value.SHORT:
buff.put((byte) type);
buff.putShort(v.getShort());
break;
case Value.INT: {
int x = v.getInt();
if (x < 0) {
buff.put((byte) INT_NEG);
writeVarInt(buff, -x);
} else if (x < 16) {
buff.put((byte) (INT_0_15 + x));
} else {
buff.put((byte) type);
writeVarInt(buff, x);
}
break;
}
case Value.LONG: {
long x = v.getLong();
if (x < 0) {
buff.put((byte) LONG_NEG);
writeVarLong(buff, -x);
} else if (x < 8) {
buff.put((byte) (LONG_0_7 + x));
} else {
buff.put((byte) type);
writeVarLong(buff, x);
}
break;
}
case Value.DECIMAL: {
BigDecimal x = v.getBigDecimal();
if (BigDecimal.ZERO.equals(x)) {
buff.put((byte) DECIMAL_0_1);
} else if (BigDecimal.ONE.equals(x)) {
buff.put((byte) (DECIMAL_0_1 + 1));
} else {
int scale = x.scale();
BigInteger b = x.unscaledValue();
int bits = b.bitLength();
if (bits <= 63) {
if (scale == 0) {
buff.put((byte) DECIMAL_SMALL_0);
writeVarLong(buff, b.longValue());
} else {
buff.put((byte) DECIMAL_SMALL);
writeVarInt(buff, scale);
writeVarLong(buff, b.longValue());
}
} else {
buff.put((byte) type);
writeVarInt(buff, scale);
byte[] bytes = b.toByteArray();
writeVarInt(buff, bytes.length);
buff = DataUtils.ensureCapacity(buff, bytes.length);
buff.put(bytes, 0, bytes.length);
}
}
break;
}
case Value.TIME:
if (SysProperties.STORE_LOCAL_TIME) {
buff.put((byte) LOCAL_TIME);
ValueTime t = (ValueTime) v;
long nanos = t.getNanos();
long millis = nanos / 1000000;
nanos -= millis * 1000000;
writeVarLong(buff, millis);
writeVarLong(buff, nanos);
} else {
buff.put((byte) type);
writeVarLong(buff, DateTimeUtils.getTimeLocalWithoutDst(v.getTime()));
}
break;
case Value.DATE: {
if (SysProperties.STORE_LOCAL_TIME) {
buff.put((byte) LOCAL_DATE);
long x = ((ValueDate) v).getDateValue();
writeVarLong(buff, x);
} else {
buff.put((byte) type);
long x = DateTimeUtils.getTimeLocalWithoutDst(v.getDate());
writeVarLong(buff, x / MILLIS_PER_MINUTE);
}
break;
}
case Value.TIMESTAMP: {
if (SysProperties.STORE_LOCAL_TIME) {
buff.put((byte) LOCAL_TIMESTAMP);
ValueTimestamp ts = (ValueTimestamp) v;
long dateValue = ts.getDateValue();
writeVarLong(buff, dateValue);
long nanos = ts.getNanos();
long millis = nanos / 1000000;
nanos -= millis * 1000000;
writeVarLong(buff, millis);
writeVarLong(buff, nanos);
} else {
Timestamp ts = v.getTimestamp();
buff.put((byte) type);
writeVarLong(buff, DateTimeUtils.getTimeLocalWithoutDst(ts));
writeVarInt(buff, ts.getNanos());
}
break;
}
case Value.JAVA_OBJECT: {
buff.put((byte) type);
byte[] b = v.getBytesNoCopy();
writeVarInt(buff, b.length);
buff = DataUtils.ensureCapacity(buff, b.length);
buff.put(b, 0, b.length);
break;
}
case Value.BYTES: {
byte[] b = v.getBytesNoCopy();
int len = b.length;
if (len < 32) {
buff.put((byte) (BYTES_0_31 + len));
buff.put(b, 0, b.length);
} else {
buff.put((byte) type);
writeVarInt(buff, b.length);
buff = DataUtils.ensureCapacity(buff, b.length);
buff.put(b, 0, b.length);
}
break;
}
case Value.UUID: {
buff.put((byte) type);
ValueUuid uuid = (ValueUuid) v;
buff.putLong(uuid.getHigh());
buff.putLong(uuid.getLow());
break;
}
case Value.STRING: {
String s = v.getString();
int len = s.length();
if (len < 32) {
buff.put((byte) (STRING_0_31 + len));
buff = writeStringWithoutLength(buff, s, len);
} else {
buff.put((byte) type);
buff = writeString(buff, s);
}
break;
}
case Value.STRING_IGNORECASE:
case Value.STRING_FIXED:
buff.put((byte) type);
buff = writeString(buff, v.getString());
break;
case Value.DOUBLE: {
double x = v.getDouble();
if (x == 1.0d) {
buff.put((byte) (DOUBLE_0_1 + 1));
} else {
long d = Double.doubleToLongBits(x);
if (d == ValueDouble.ZERO_BITS) {
buff.put((byte) DOUBLE_0_1);
} else {
buff.put((byte) type);
writeVarLong(buff, Long.reverse(d));
}
}
break;
}
case Value.FLOAT: {
float x = v.getFloat();
if (x == 1.0f) {
buff.put((byte) (FLOAT_0_1 + 1));
} else {
int f = Float.floatToIntBits(x);
if (f == ValueFloat.ZERO_BITS) {
buff.put((byte) FLOAT_0_1);
} else {
buff.put((byte) type);
writeVarInt(buff, Integer.reverse(f));
}
}
break;
}
case Value.BLOB:
case Value.CLOB: {
buff.put((byte) type);
if (v instanceof ValueLob) {
ValueLob lob = (ValueLob) v;
lob.convertToFileIfRequired(handler);
byte[] small = lob.getSmall();
if (small == null) {
int t = -1;
if (!lob.isLinked()) {
t = -2;
}
writeVarInt(buff, t);
writeVarInt(buff, lob.getTableId());
writeVarInt(buff, lob.getObjectId());
writeVarLong(buff, lob.getPrecision());
buff.put((byte) (lob.useCompression() ? 1 : 0));
if (t == -2) {
buff = writeString(buff, lob.getFileName());
}
} else {
writeVarInt(buff, small.length);
buff = DataUtils.ensureCapacity(buff, small.length);
buff.put(small, 0, small.length);
}
} else {
ValueLobDb lob = (ValueLobDb) v;
byte[] small = lob.getSmall();
if (small == null) {
writeVarInt(buff, -3);
writeVarInt(buff, lob.getTableId());
writeVarLong(buff, lob.getLobId());
writeVarLong(buff, lob.getPrecision());
} else {
writeVarInt(buff, small.length);
buff = DataUtils.ensureCapacity(buff, small.length);
buff.put(small, 0, small.length);
}
}
break;
}
case Value.ARRAY: {
buff.put((byte) type);
Value[] list = ((ValueArray) v).getList();
writeVarInt(buff, list.length);
for (Value x : list) {
buff = DataUtils.ensureCapacity(buff, 0);
buff = writeValue(buff, x);
}
break;
}
case Value.RESULT_SET: {
buff.put((byte) type);
try {
ResultSet rs = ((ValueResultSet) v).getResultSet();
rs.beforeFirst();
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
writeVarInt(buff, columnCount);
for (int i = 0; i < columnCount; i++) {
buff = DataUtils.ensureCapacity(buff, 0);
buff = writeString(buff, meta.getColumnName(i + 1));
writeVarInt(buff, meta.getColumnType(i + 1));
writeVarInt(buff, meta.getPrecision(i + 1));
writeVarInt(buff, meta.getScale(i + 1));
}
while (rs.next()) {
buff.put((byte) 1);
for (int i = 0; i < columnCount; i++) {
int t = org.h2.value.DataType.convertSQLTypeToValueType(meta.getColumnType(i + 1));
Value val = org.h2.value.DataType.readValue(null, rs, i + 1, t);
buff = writeValue(buff, val);
}
}
buff.put((byte) 0);
rs.beforeFirst();
} catch (SQLException e) {
throw DbException.convert(e);
}
break;
}
default:
DbException.throwInternalError("type=" + v.getType());
}
if (SysProperties.CHECK2) {
if (buff.position() - start != Data.getValueLen(v, handler)) {
throw DbException
.throwInternalError("value size error: got " + (buff.position() - start) + " expected " + Data.getValueLen(v, handler));
}
}
return buff;
}
private static void writeVarInt(ByteBuffer buff, int x) {
while ((x & ~0x7f) != 0) {
buff.put((byte) (0x80 | (x & 0x7f)));
x >>>= 7;
}
buff.put((byte) x);
}
private static void writeVarLong(ByteBuffer buff, long x) {
while ((x & ~0x7f) != 0) {
buff.put((byte) ((x & 0x7f) | 0x80));
x >>>= 7;
}
buff.put((byte) x);
}
private static ByteBuffer writeString(ByteBuffer buff, String s) {
int len = s.length();
writeVarInt(buff, len);
return writeStringWithoutLength(buff, s, len);
}
private static ByteBuffer writeStringWithoutLength(ByteBuffer buff, String s, int len) {
buff = DataUtils.ensureCapacity(buff, 3 * len);
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (c < 0x80) {
buff.put((byte) c);
} else if (c >= 0x800) {
buff.put((byte) (0xe0 | (c >> 12)));
buff.put((byte) (((c >> 6) & 0x3f)));
buff.put((byte) (c & 0x3f));
} else {
buff.put((byte) (0xc0 | (c >> 6)));
buff.put((byte) (c & 0x3f));
}
}
return buff;
}
/**
* Read a value.
*
* @return the value
*/
private Value readValue(ByteBuffer buff) {
int type = buff.get() & 255;
switch (type) {
case Value.NULL:
return ValueNull.INSTANCE;
case BOOLEAN_TRUE:
return ValueBoolean.get(true);
case BOOLEAN_FALSE:
return ValueBoolean.get(false);
case INT_NEG:
return ValueInt.get(-readVarInt(buff));
case Value.INT:
return ValueInt.get(readVarInt(buff));
case LONG_NEG:
return ValueLong.get(-readVarLong(buff));
case Value.LONG:
return ValueLong.get(readVarLong(buff));
case Value.BYTE:
return ValueByte.get(buff.get());
case Value.SHORT:
return ValueShort.get(buff.getShort());
case DECIMAL_0_1:
return (ValueDecimal) ValueDecimal.ZERO;
case DECIMAL_0_1 + 1:
return (ValueDecimal) ValueDecimal.ONE;
case DECIMAL_SMALL_0:
return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff)));
case DECIMAL_SMALL: {
int scale = readVarInt(buff);
return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff), scale));
}
case Value.DECIMAL: {
int scale = readVarInt(buff);
int len = readVarInt(buff);
byte[] buff2 = DataUtils.newBytes(len);
buff.get(buff2, 0, len);
BigInteger b = new BigInteger(buff2);
return ValueDecimal.get(new BigDecimal(b, scale));
}
case LOCAL_DATE: {
return ValueDate.fromDateValue(readVarLong(buff));
}
case Value.DATE: {
long x = readVarLong(buff) * MILLIS_PER_MINUTE;
return ValueDate.get(new Date(DateTimeUtils.getTimeUTCWithoutDst(x)));
}
case LOCAL_TIME: {
long nanos = readVarLong(buff) * 1000000 + readVarLong(buff);
return ValueTime.fromNanos(nanos);
}
case Value.TIME:
// need to normalize the year, month and day
return ValueTime.get(new Time(DateTimeUtils.getTimeUTCWithoutDst(readVarLong(buff))));
case LOCAL_TIMESTAMP: {
long dateValue = readVarLong(buff);
long nanos = readVarLong(buff) * 1000000 + readVarLong(buff);
return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
}
case Value.TIMESTAMP: {
Timestamp ts = new Timestamp(DateTimeUtils.getTimeUTCWithoutDst(readVarLong(buff)));
ts.setNanos(readVarInt(buff));
return ValueTimestamp.get(ts);
}
case Value.BYTES: {
int len = readVarInt(buff);
byte[] b = DataUtils.newBytes(len);
buff.get(b, 0, len);
return ValueBytes.getNoCopy(b);
}
case Value.JAVA_OBJECT: {
int len = readVarInt(buff);
byte[] b = DataUtils.newBytes(len);
buff.get(b, 0, len);
return ValueJavaObject.getNoCopy(null, b);
}
case Value.UUID:
return ValueUuid.get(buff.getLong(), buff.getLong());
case Value.STRING:
return ValueString.get(readString(buff));
case Value.STRING_IGNORECASE:
return ValueStringIgnoreCase.get(readString(buff));
case Value.STRING_FIXED:
return ValueStringFixed.get(readString(buff));
case FLOAT_0_1:
return ValueFloat.get(0);
case FLOAT_0_1 + 1:
return ValueFloat.get(1);
case DOUBLE_0_1:
return ValueDouble.get(0);
case DOUBLE_0_1 + 1:
return ValueDouble.get(1);
case Value.DOUBLE:
return ValueDouble.get(Double.longBitsToDouble(Long.reverse(readVarLong(buff))));
case Value.FLOAT:
return ValueFloat.get(Float.intBitsToFloat(Integer.reverse(readVarInt(buff))));
case Value.BLOB:
case Value.CLOB: {
int smallLen = readVarInt(buff);
if (smallLen >= 0) {
byte[] small = DataUtils.newBytes(smallLen);
buff.get(small, 0, smallLen);
return LobStorage.createSmallLob(type, small);
} else if (smallLen == -3) {
int tableId = readVarInt(buff);
long lobId = readVarLong(buff);
long precision = readVarLong(buff);
LobStorage lobStorage = handler.getLobStorage();
ValueLobDb lob = ValueLobDb.create(type, lobStorage, tableId, lobId, null, precision);
return lob;
} else {
int tableId = readVarInt(buff);
int objectId = readVarInt(buff);
long precision = 0;
boolean compression = false;
// -1: regular
// -2: regular, but not linked (in this case: including file name)
if (smallLen == -1 || smallLen == -2) {
precision = readVarLong(buff);
compression = buff.get() == 1;
}
ValueLob lob = ValueLob.open(type, handler, tableId, objectId, precision, compression);
if (smallLen == -2) {
lob.setFileName(readString(buff), false);
}
return lob;
}
}
case Value.ARRAY: {
int len = readVarInt(buff);
Value[] list = new Value[len];
for (int i = 0; i < len; i++) {
list[i] = readValue(buff);
}
return ValueArray.get(list);
}
case Value.RESULT_SET: {
SimpleResultSet rs = new SimpleResultSet();
int columns = readVarInt(buff);
for (int i = 0; i < columns; i++) {
rs.addColumn(readString(buff), readVarInt(buff), readVarInt(buff), readVarInt(buff));
}
while (true) {
if (buff.get() == 0) {
break;
}
Object[] o = new Object[columns];
for (int i = 0; i < columns; i++) {
o[i] = readValue(buff).getObject();
}
rs.addRow(o);
}
return ValueResultSet.get(rs);
}
default:
if (type >= INT_0_15 && type < INT_0_15 + 16) {
return ValueInt.get(type - INT_0_15);
} else if (type >= LONG_0_7 && type < LONG_0_7 + 8) {
return ValueLong.get(type - LONG_0_7);
} else if (type >= BYTES_0_31 && type < BYTES_0_31 + 32) {
int len = type - BYTES_0_31;
byte[] b = DataUtils.newBytes(len);
buff.get(b, 0, len);
return ValueBytes.getNoCopy(b);
} else if (type >= STRING_0_31 && type < STRING_0_31 + 32) {
return ValueString.get(readString(buff, type - STRING_0_31));
}
throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "type: " + type);
}
}
private static int readVarInt(ByteBuffer buff) {
return DataUtils.readVarInt(buff);
}
private static long readVarLong(ByteBuffer buff) {
return DataUtils.readVarLong(buff);
}
private static String readString(ByteBuffer buff, int len) {
return DataUtils.readString(buff, len);
}
private static String readString(ByteBuffer buff) {
int len = readVarInt(buff);
return DataUtils.readString(buff, len);
}
}
......@@ -976,7 +976,7 @@ HASH(algorithmString, dataBytes, iterationInt)
","
Calculate the hash value using an algorithm, and repeat this process for a number of iterations."
"Functions (Numeric)","TRUNCATE","
{ TRUNC | TRUNCATE } (numeric, digitsInt)
{ TRUNC | TRUNCATE } ({numeric, digitsInt} | timestamp)
","
Truncates to a number of digits (to the next value closer to 0)."
"Functions (Numeric)","COMPRESS","
......@@ -1223,10 +1223,6 @@ Returns the quarter (1-4) from a timestamp."
SECOND(timestamp)
","
Returns the second (0-59) from a timestamp."
"Functions (Time and Date)","TRUNCATE","
TRUNCATE(timestamp)
","
Truncates a timestamp to a date (day) value."
"Functions (Time and Date)","WEEK","
WEEK(timestamp)
","
......
......@@ -1087,4 +1087,8 @@ public abstract class Table extends SchemaObjectBase {
this.isHidden = hidden;
}
public boolean isMVStore() {
return false;
}
}
......@@ -36,7 +36,7 @@ public class TestMVTableEngine extends TestBase {
public void test() throws Exception {
testEncryption();
testReadOnly();
testReuseDiskSpace();
// testReuseDiskSpace();
testDataTypes();
testLocking();
testSimple();
......@@ -103,7 +103,7 @@ public class TestMVTableEngine extends TestBase {
if (i < 6) {
maxSizeHalf = Math.max(size, maxSizeHalf);
} else if (size > maxSizeHalf) {
fail("size: " + size + " max: " + maxSizeHalf);
fail(i + " size: " + size + " max: " + maxSizeHalf);
}
}
}
......
......@@ -38,6 +38,8 @@ public class TestTransactionStore extends TestBase {
}
public void test() throws Exception {
FileUtils.createDirectories(getBaseDir());
testMultiStatement();
testTwoPhaseCommit();
testSavepoint();
......@@ -69,7 +71,8 @@ public class TestTransactionStore extends TestBase {
// start of statement
// create table test
startUpdate = tx.setSavepoint();
tx.openMap("test");
version = s.getCurrentVersion();
tx.openMap("test", version);
// start of statement
// insert into test(id, name) values(1, 'Hello'), (2, 'World')
......@@ -174,18 +177,14 @@ public class TestTransactionStore extends TestBase {
m = tx.openMap("test");
assertEquals(null, m.get("1"));
list = ts.getOpenTransactions();
assertEquals(2, list.size());
// only one was writing
assertEquals(1, list.size());
txOld = list.get(0);
assertEquals(0, txOld.getId());
assertEquals(Transaction.STATUS_OPEN, txOld.getStatus());
assertEquals("first transaction", txOld.getName());
txOld.prepare();
assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus());
txOld = list.get(1);
assertEquals(1, txOld.getId());
assertNull(txOld.getName());
assertEquals(Transaction.STATUS_OPEN, txOld.getStatus());
txOld.rollback();
s.commit();
s.close();
......@@ -196,7 +195,7 @@ public class TestTransactionStore extends TestBase {
// TransactionStore was not closed, so we lost some ids
assertEquals(33, tx.getId());
list = ts.getOpenTransactions();
assertEquals(2, list.size());
assertEquals(1, list.size());
txOld = list.get(0);
assertEquals(0, txOld.getId());
assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus());
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论