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

MVStore: support statements (updates should not need to first remove, and then re-add all entries)

上级 6ff5798f
...@@ -583,14 +583,21 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -583,14 +583,21 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/ */
public synchronized boolean remove(Object key, Object value) { public synchronized boolean remove(Object key, Object value) {
V old = get(key); V old = get(key);
if (equalsValue(old, value)) { if (areValuesEqual(old, value)) {
remove(key); remove(key);
return true; return true;
} }
return false; return false;
} }
private boolean equalsValue(Object a, Object b) { /**
* Check whether the two values are equal.
*
* @param a the first value
* @param b the second value
* @return true if they are equal
*/
public boolean areValuesEqual(Object a, Object b) {
if (a == b) { if (a == b) {
return true; return true;
} else if (a == null || b == null) { } else if (a == null || b == null) {
...@@ -609,7 +616,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -609,7 +616,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/ */
public synchronized boolean replace(K key, V oldValue, V newValue) { public synchronized boolean replace(K key, V oldValue, V newValue) {
V old = get(key); V old = get(key);
if (equalsValue(old, oldValue)) { if (areValuesEqual(old, oldValue)) {
put(key, newValue); put(key, newValue);
return true; return true;
} }
......
...@@ -391,8 +391,6 @@ public class TransactionStore { ...@@ -391,8 +391,6 @@ public class TransactionStore {
*/ */
public <K, V> TransactionMap<K, V> openMap(String name, long readVersion) { public <K, V> TransactionMap<K, V> openMap(String name, long readVersion) {
checkOpen(); checkOpen();
// TODO read from a stable version of the data within
// one 'statement'
return new TransactionMap<K, V>(this, name, readVersion); return new TransactionMap<K, V>(this, name, readVersion);
} }
...@@ -463,14 +461,19 @@ public class TransactionStore { ...@@ -463,14 +461,19 @@ public class TransactionStore {
/** /**
* The map used for writing (the latest version). * The map used for writing (the latest version).
* <p>
* Key: key the key of the data. * Key: key the key of the data.
* Value: { transactionId, oldVersion, value } * Value: { transactionId, oldVersion, value }
*/ */
private final MVMap<K, Object[]> mapWrite; private final MVMap<K, Object[]> mapWrite;
/** /**
* The map used for reading (possibly an older version). * The map used for reading (possibly an older version). Reading is done
* Key: key the key of the data. * on an older version so that changes are not immediately visible, to
* support statement processing (for example
* "update test set id = id + 1").
* <p>
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value } * Value: { transactionId, oldVersion, value }
*/ */
private final MVMap<K, Object[]> mapRead; private final MVMap<K, Object[]> mapRead;
...@@ -511,8 +514,8 @@ public class TransactionStore { ...@@ -511,8 +514,8 @@ public class TransactionStore {
/** /**
* Remove an entry. * Remove an entry.
* <p> * <p>
* If the row is locked, this method * If the row is locked, this method will retry until the row could be
* will retry until the row could be updated or until a lock timeout. * updated or until a lock timeout.
* *
* @param key the key * @param key the key
* @throws IllegalStateException if a lock timeout occurs * @throws IllegalStateException if a lock timeout occurs
...@@ -524,11 +527,11 @@ public class TransactionStore { ...@@ -524,11 +527,11 @@ public class TransactionStore {
/** /**
* Update the value for the given key. * Update the value for the given key.
* <p> * <p>
* If the row is locked, this method * If the row is locked, this method will retry until the row could be
* will retry until the row could be updated or until a lock timeout. * updated or until a lock timeout.
* *
* @param key the key * @param key the key
* @param value the new value (null to remove the row) * @param value the new value (not null)
* @throws IllegalStateException if a lock timeout occurs * @throws IllegalStateException if a lock timeout occurs
*/ */
public V put(K key, V value) { public V put(K key, V value) {
...@@ -536,12 +539,12 @@ public class TransactionStore { ...@@ -536,12 +539,12 @@ public class TransactionStore {
return set(key, value); return set(key, value);
} }
public V set(K key, V value) { private V set(K key, V value) {
checkOpen(); checkOpen();
long start = 0; long start = 0;
while (true) { while (true) {
V old = get(key); V old = get(key);
boolean ok = trySet(key, value); boolean ok = trySet(key, value, false);
if (ok) { if (ok) {
return old; return old;
} }
...@@ -578,7 +581,7 @@ public class TransactionStore { ...@@ -578,7 +581,7 @@ public class TransactionStore {
* @return whether the entry could be removed * @return whether the entry could be removed
*/ */
public boolean tryRemove(K key) { public boolean tryRemove(K key) {
return trySet(key, null); return trySet(key, null, false);
} }
/** /**
...@@ -593,11 +596,29 @@ public class TransactionStore { ...@@ -593,11 +596,29 @@ public class TransactionStore {
*/ */
public boolean tryPut(K key, V value) { public boolean tryPut(K key, V value) {
DataUtils.checkArgument(value != null, "The value may not be null"); DataUtils.checkArgument(value != null, "The value may not be null");
return trySet(key, value); return trySet(key, value, false);
} }
private boolean trySet(K key, V value) { /**
* Try to set or remove the value. When updating only unchanged entries,
* then the value is only changed if it was not changed after opening
* the map.
*
* @param key the key
* @param value the new value (null to remove the value)
* @param onlyIfUnchanged only set the value if it was not changed (by
* this or another transaction) since the map was opened
* @return true if the value was set
*/
public boolean trySet(K key, V value, boolean onlyIfUnchanged) {
MVMap<K, Object[]> m = mapRead;
Object[] current = mapWrite.get(key); Object[] current = mapWrite.get(key);
if (onlyIfUnchanged) {
Object[] old = m.get(key);
if (!mapWrite.areValuesEqual(old, current)) {
return false;
}
}
long oldVersion = transaction.store.store.getCurrentVersion() - 1; long oldVersion = transaction.store.store.getCurrentVersion() - 1;
int opType; int opType;
if (current == null || current[2] == null) { if (current == null || current[2] == null) {
...@@ -625,7 +646,7 @@ public class TransactionStore { ...@@ -625,7 +646,7 @@ public class TransactionStore {
} }
return false; return false;
} }
long tx = ((Long) current[0]).longValue(); long tx = (Long) current[0];
if (tx == transaction.transactionId) { if (tx == transaction.transactionId) {
// added or updated by this transaction // added or updated by this transaction
if (mapWrite.replace(key, current, newValue)) { if (mapWrite.replace(key, current, newValue)) {
...@@ -659,6 +680,26 @@ public class TransactionStore { ...@@ -659,6 +680,26 @@ public class TransactionStore {
return false; return false;
} }
/**
* Get the value for the given key at the time when this map was opened.
*
* @param key the key
* @return the value or null
*/
public V get(K key) {
return get(key, mapRead);
}
/**
* Get the most recent value for the given key.
*
* @param key the key
* @return the value or null
*/
public V getLatest(K key) {
return get(key, mapWrite);
}
/** /**
* Get the value for the given key. * Get the value for the given key.
* *
...@@ -666,10 +707,8 @@ public class TransactionStore { ...@@ -666,10 +707,8 @@ public class TransactionStore {
* @return the value or null * @return the value or null
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public public V get(K key, MVMap<K, Object[]> m) {
V get(K key) {
checkOpen(); checkOpen();
MVMap<K, Object[]> m = mapRead;
while (true) { while (true) {
Object[] data = m.get(key); Object[] data = m.get(key);
long tx; long tx;
...@@ -677,7 +716,7 @@ public class TransactionStore { ...@@ -677,7 +716,7 @@ public class TransactionStore {
// doesn't exist or deleted by a committed transaction // doesn't exist or deleted by a committed transaction
return null; return null;
} }
tx = ((Long) data[0]).longValue(); tx = (Long) data[0];
if (tx == transaction.transactionId) { if (tx == transaction.transactionId) {
// added by this transaction // added by this transaction
return (V) data[2]; return (V) data[2];
...@@ -688,7 +727,7 @@ public class TransactionStore { ...@@ -688,7 +727,7 @@ public class TransactionStore {
// it is committed // it is committed
return (V) data[2]; return (V) data[2];
} }
tx = ((Long) data[0]).longValue(); tx = (Long) data[0];
// get the value before the uncommitted transaction // get the value before the uncommitted transaction
if (data[1] == null) { if (data[1] == null) {
// a new entry // a new entry
...@@ -698,7 +737,6 @@ public class TransactionStore { ...@@ -698,7 +737,6 @@ public class TransactionStore {
m = mapWrite.openVersion(oldVersion); m = mapWrite.openVersion(oldVersion);
} }
} }
} }
} }
......
...@@ -38,7 +38,7 @@ public class TestTransactionStore extends TestBase { ...@@ -38,7 +38,7 @@ public class TestTransactionStore extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
// testMultiStatement(); testMultiStatement();
testTwoPhaseCommit(); testTwoPhaseCommit();
testSavepoint(); testSavepoint();
testConcurrentTransactionsReadCommitted(); testConcurrentTransactionsReadCommitted();
...@@ -51,59 +51,77 @@ public class TestTransactionStore extends TestBase { ...@@ -51,59 +51,77 @@ public class TestTransactionStore extends TestBase {
* uses a savepoint. Within a statement, a change by the statement itself is * uses a savepoint. Within a statement, a change by the statement itself is
* not seen; the change is only seen when the statement finished. * not seen; the change is only seen when the statement finished.
*/ */
// private void testMultiStatement() { private void testMultiStatement() {
// MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
// TransactionStore ts = new TransactionStore(s); TransactionStore ts = new TransactionStore(s);
// Transaction tx; Transaction tx;
// TransactionMap<String, String> m; TransactionMap<String, String> m;
// long startUpdate; long startUpdate;
// long version;
// tx = ts.begin();
// tx = ts.begin();
// // start of statement
// // insert into test(id, name) values(1, 'Hello'), (2, 'World') // TODO support and test rollback of table creation / removal
// startUpdate = tx.setSavepoint();
// m = tx.openMap("test", startUpdate); // start of statement
// TODO putIfAbsent // create table test
// m.put("1", "Hello"); startUpdate = tx.setSavepoint();
// m.put("2", "World"); tx.openMap("test");
// // not seen yet
// assertNull(m.get("1")); // start of statement
// // insert into test(id, name) values(1, 'Hello'), (2, 'World')
// // start of statement startUpdate = tx.setSavepoint();
// startUpdate = tx.setSavepoint(); version = s.getCurrentVersion();
// // update test set primaryKey = primaryKey + 1 m = tx.openMap("test", version);
// m = tx.openMap("test", startUpdate); assertTrue(m.trySet("1", "Hello", true));
// assertTrue(m.trySet("2", "World", true));
// for (Cursor) // not seen yet (within the same statement)
// assertNull(m.get("1"));
// tx.commit(); assertNull(m.get("2"));
//
// // start of statement
// startUpdate = tx.setSavepoint();
// m.put("2", "World"); version = s.getCurrentVersion();
// m.put("1", "Hallo"); // now we see the newest version
// m.remove("2"); m = tx.openMap("test", version);
// m.put("3", "!"); assertEquals("Hello", m.get("1"));
// long logId = tx.setSavepoint(); assertEquals("World", m.get("2"));
// m.put("1", "Hi"); // update test set primaryKey = primaryKey + 1
// m.put("2", "."); // (this is usually a tricky cases)
// m.remove("3"); assertEquals("Hello", m.get("1"));
// tx.rollbackToSavepoint(logId); assertTrue(m.trySet("1", null, true));
// assertEquals("Hallo", m.get("1")); assertTrue(m.trySet("2", "Hello", true));
// assertNull(m.get("2")); // already updated by this statement, so it has no effect
// assertEquals("!", m.get("3")); // but still returns true because it was changed by this transaction
// tx.rollback(); assertEquals("World", m.get("2"));
// assertTrue(m.trySet("2", null, true));
// tx = ts.begin(); assertTrue(m.trySet("3", "World", true));
// m = tx.openMap("test"); // not seen within this statement
// assertNull(m.get("1")); assertEquals("Hello", m.get("1"));
// assertNull(m.get("2")); assertEquals("World", m.get("2"));
// assertNull(m.get("3")); assertNull(m.get("3"));
//
// ts.close(); // start of statement
// s.close(); startUpdate = tx.setSavepoint();
// } version = s.getCurrentVersion();
m = tx.openMap("test", version);
// select * from test
assertNull(m.get("1"));
assertEquals("Hello", m.get("2"));
assertEquals("World", m.get("3"));
// start of statement
startUpdate = tx.setSavepoint();
version = s.getCurrentVersion();
m = tx.openMap("test", version);
// update test set id = 1
// TODO should fail: duplicate key
tx.commit();
ts.close();
s.close();
}
private void testTwoPhaseCommit() throws Exception { private void testTwoPhaseCommit() throws Exception {
String fileName = getBaseDir() + "/testTwoPhaseCommit.h3"; String fileName = getBaseDir() + "/testTwoPhaseCommit.h3";
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论