Unverified 提交 d6ad398c authored 作者: Andrei Tokar's avatar Andrei Tokar 提交者: GitHub

Merge pull request #1058 from h2database/txcommit-atomic

Txcommit atomic
...@@ -737,6 +737,9 @@ public class Database implements DataHandler { ...@@ -737,6 +737,9 @@ public class Database implements DataHandler {
systemUser.setAdmin(true); systemUser.setAdmin(true);
systemSession = new Session(this, systemUser, ++nextSessionId); systemSession = new Session(this, systemUser, ++nextSessionId);
lobSession = new Session(this, systemUser, ++nextSessionId); lobSession = new Session(this, systemUser, ++nextSessionId);
if(mvStore != null) {
mvStore.getTransactionStore().init(systemSession);
}
CreateTableData data = new CreateTableData(); CreateTableData data = new CreateTableData();
ArrayList<Column> cols = data.columns; ArrayList<Column> cols = data.columns;
Column columnId = new Column("ID", Value.INT); Column columnId = new Column("ID", Value.INT);
...@@ -762,6 +765,7 @@ public class Database implements DataHandler { ...@@ -762,6 +765,7 @@ public class Database implements DataHandler {
metaIdIndex = meta.addIndex(systemSession, "SYS_ID", metaIdIndex = meta.addIndex(systemSession, "SYS_ID",
0, pkCols, IndexType.createPrimaryKey( 0, pkCols, IndexType.createPrimaryKey(
false, false), true, null); false, false), true, null);
systemSession.commit(true);
objectIds.set(0); objectIds.set(0);
starting = true; starting = true;
Cursor cursor = metaIdIndex.find(systemSession, null, null); Cursor cursor = metaIdIndex.find(systemSession, null, null);
...@@ -777,6 +781,7 @@ public class Database implements DataHandler { ...@@ -777,6 +781,7 @@ public class Database implements DataHandler {
rec.execute(this, systemSession, eventListener); rec.execute(this, systemSession, eventListener);
} }
} }
systemSession.commit(true);
if (mvStore != null) { if (mvStore != null) {
mvStore.initTransactions(); mvStore.initTransactions();
mvStore.removeTemporaryMaps(objectIds); mvStore.removeTemporaryMaps(objectIds);
......
...@@ -30,10 +30,12 @@ import org.h2.jdbc.JdbcConnection; ...@@ -30,10 +30,12 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.db.MVTable; import org.h2.mvstore.db.MVTable;
import org.h2.mvstore.db.MVTableEngine; import org.h2.mvstore.db.MVTableEngine;
import org.h2.mvstore.tx.TransactionStore.Change; import org.h2.mvstore.tx.TransactionStore;
import org.h2.mvstore.tx.Transaction; import org.h2.mvstore.tx.Transaction;
import org.h2.mvstore.tx.VersionedValue;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -59,7 +61,7 @@ import org.h2.value.ValueString; ...@@ -59,7 +61,7 @@ import org.h2.value.ValueString;
* mode, this object resides on the server side and communicates with a * mode, this object resides on the server side and communicates with a
* SessionRemote object on the client side. * SessionRemote object on the client side.
*/ */
public class Session extends SessionWithState { public class Session extends SessionWithState implements TransactionStore.RollbackListener {
/** /**
* This special log position means that the log entry has been written. * This special log position means that the log entry has been written.
...@@ -649,6 +651,7 @@ public class Session extends SessionWithState { ...@@ -649,6 +651,7 @@ public class Session extends SessionWithState {
currentTransactionName = null; currentTransactionName = null;
transactionStart = 0; transactionStart = 0;
if (transaction != null) { if (transaction != null) {
try {
// increment the data mod count, so that other sessions // increment the data mod count, so that other sessions
// see the changes // see the changes
// TODO should not rely on locking // TODO should not rely on locking
...@@ -660,9 +663,10 @@ public class Session extends SessionWithState { ...@@ -660,9 +663,10 @@ public class Session extends SessionWithState {
} }
} }
transaction.commit(); transaction.commit();
} finally {
transaction = null; transaction = null;
} }
if (containsUncommitted()) { } else if (containsUncommitted()) {
// need to commit even if rollback is not possible // need to commit even if rollback is not possible
// (create/drop table and so on) // (create/drop table and so on)
database.commit(this); database.commit(this);
...@@ -768,18 +772,9 @@ public class Session extends SessionWithState { ...@@ -768,18 +772,9 @@ public class Session extends SessionWithState {
checkCommitRollback(); checkCommitRollback();
currentTransactionName = null; currentTransactionName = null;
transactionStart = 0; transactionStart = 0;
boolean needCommit = false; boolean needCommit = undoLog.size() > 0 || transaction != null;
if (undoLog.size() > 0) { if(needCommit) {
rollbackTo(null, false);
needCommit = true;
}
if (transaction != null) {
rollbackTo(null, false); rollbackTo(null, false);
needCommit = true;
// rollback stored the undo operations in the transaction
// committing will end the transaction
transaction.commit();
transaction = null;
} }
if (!locks.isEmpty() || needCommit) { if (!locks.isEmpty() || needCommit) {
database.commit(this); database.commit(this);
...@@ -806,29 +801,11 @@ public class Session extends SessionWithState { ...@@ -806,29 +801,11 @@ public class Session extends SessionWithState {
undoLog.removeLast(trimToSize); undoLog.removeLast(trimToSize);
} }
if (transaction != null) { if (transaction != null) {
long savepointId = savepoint == null ? 0 : savepoint.transactionSavepoint; if (savepoint == null) {
HashMap<String, MVTable> tableMap = transaction.rollback();
database.getMvStore().getTables(); transaction = null;
Iterator<Change> it = transaction.getChanges(savepointId);
while (it.hasNext()) {
Change c = it.next();
MVTable t = tableMap.get(c.mapName);
if (t != null) {
long key = ((ValueLong) c.key).getLong();
ValueArray value = (ValueArray) c.value;
short op;
Row row;
if (value == null) {
op = UndoLogRecord.INSERT;
row = t.getRow(this, key);
} else { } else {
op = UndoLogRecord.DELETE; transaction.rollbackToSavepoint(savepoint.transactionSavepoint);
row = createRow(value.getList(), Row.MEMORY_CALCULATE);
}
row.setKey(key);
UndoLogRecord log = new UndoLogRecord(t, op, row);
log.undo(this);
}
} }
} }
if (savepoints != null) { if (savepoints != null) {
...@@ -841,6 +818,13 @@ public class Session extends SessionWithState { ...@@ -841,6 +818,13 @@ public class Session extends SessionWithState {
} }
} }
} }
// Because cache may have captured query result (in Query.lastResult),
// which is based on data from uncommitted transaction.,
// It is not valid after rollback, therefore cache has to be cleared.
if(queryCache != null) {
queryCache.clear();
}
} }
@Override @Override
...@@ -1112,7 +1096,7 @@ public class Session extends SessionWithState { ...@@ -1112,7 +1096,7 @@ public class Session extends SessionWithState {
*/ */
public boolean containsUncommitted() { public boolean containsUncommitted() {
if (database.getMvStore() != null) { if (database.getMvStore() != null) {
return transaction != null; return transaction != null && transaction.hasChanges();
} }
return firstUncommittedLog != Session.LOG_WRITTEN; return firstUncommittedLog != Session.LOG_WRITTEN;
} }
...@@ -1688,7 +1672,7 @@ public class Session extends SessionWithState { ...@@ -1688,7 +1672,7 @@ public class Session extends SessionWithState {
database.shutdownImmediately(); database.shutdownImmediately();
throw DbException.get(ErrorCode.DATABASE_IS_CLOSED); throw DbException.get(ErrorCode.DATABASE_IS_CLOSED);
} }
transaction = store.getTransactionStore().begin(); transaction = store.getTransactionStore().begin(this);
} }
startStatement = -1; startStatement = -1;
} }
...@@ -1768,6 +1752,62 @@ public class Session extends SessionWithState { ...@@ -1768,6 +1752,62 @@ public class Session extends SessionWithState {
tablesToAnalyze.add(table); tablesToAnalyze.add(table);
} }
@Override
public void onRollback(MVMap<Object, VersionedValue> map, Object key,
VersionedValue existingValue,
VersionedValue restoredValue) {
// Here we are relying on the fact that map which backs table's primary index
// has the same name as the table itself
MVTableEngine.Store store = database.getMvStore();
if(store != null) {
MVTable table = store.getTable(map.getName());
if (table != null) {
long recKey = ((ValueLong)key).getLong();
Row oldRow = getRowFromVersionedValue(table, recKey, existingValue);
Row newRow = getRowFromVersionedValue(table, recKey, restoredValue);
table.fireAfterRow(this, oldRow, newRow, true);
if (table.getContainsLargeObject()) {
if (oldRow != null) {
for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) {
Value v = oldRow.getValue(i);
if (v.isLinkedToTable()) {
removeAtCommit(v);
}
}
}
if (newRow != null) {
for (int i = 0, len = newRow.getColumnCount(); i < len; i++) {
Value v = newRow.getValue(i);
if (v.isLinkedToTable()) {
removeAtCommitStop(v);
}
}
}
}
}
}
}
private static Row getRowFromVersionedValue(MVTable table, long recKey,
VersionedValue versionedValue) {
Object value = versionedValue == null ? null : versionedValue.value;
if (value == null) {
return null;
}
Row result;
if(value instanceof Row) {
result = (Row) value;
assert result.getKey() == recKey : result.getKey() + " != " + recKey;
} else {
ValueArray array = (ValueArray) value;
result = table.createRow(array.getList(), 0);
result.setKey(recKey);
}
return result;
}
/** /**
* Represents a savepoint (a position in a transaction to where one can roll * Represents a savepoint (a position in a transaction to where one can roll
* back to). * back to).
......
...@@ -162,7 +162,7 @@ public class MVTableEngine implements TableEngine { ...@@ -162,7 +162,7 @@ public class MVTableEngine implements TableEngine {
this.transactionStore = new TransactionStore( this.transactionStore = new TransactionStore(
store, store,
new ValueDataType(db.getCompareMode(), db, null)); new ValueDataType(db.getCompareMode(), db, null));
transactionStore.init(); // transactionStore.init();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw convertIllegalStateException(e); throw convertIllegalStateException(e);
} }
...@@ -206,8 +206,8 @@ public class MVTableEngine implements TableEngine { ...@@ -206,8 +206,8 @@ public class MVTableEngine implements TableEngine {
return transactionStore; return transactionStore;
} }
public HashMap<String, MVTable> getTables() { public MVTable getTable(String tableName) {
return new HashMap<>(tableMap); return tableMap.get(tableName);
} }
/** /**
......
...@@ -81,13 +81,24 @@ public class Transaction { ...@@ -81,13 +81,24 @@ public class Transaction {
*/ */
final TransactionStore store; final TransactionStore store;
/**
* Listener for this transaction's rollback changes.
*/
final TransactionStore.RollbackListener listener;
/** /**
* The transaction id. * The transaction id.
* More appropriate name for this field would be "slotId"
*/ */
final int transactionId; final int transactionId;
/**
* This is really a transaction identity, because it's not re-used.
*/
public final long sequenceNum;
/* /*
* Transation state is an atomic composite field: * Transaction state is an atomic composite field:
* bit 45 : flag whether transaction had rollback(s) * bit 45 : flag whether transaction had rollback(s)
* bits 44-41 : status * bits 44-41 : status
* bits 40 : overflow control bit, 1 indicates overflow * bits 40 : overflow control bit, 1 indicates overflow
...@@ -99,12 +110,16 @@ public class Transaction { ...@@ -99,12 +110,16 @@ public class Transaction {
private String name; private String name;
Transaction(TransactionStore store, int transactionId, int status, boolean wasStored;
String name, long logId) {
Transaction(TransactionStore store, int transactionId, long sequenceNum, int status,
String name, long logId, TransactionStore.RollbackListener listener) {
this.store = store; this.store = store;
this.transactionId = transactionId; this.transactionId = transactionId;
this.sequenceNum = sequenceNum;
this.statusAndLogId = new AtomicLong(composeState(status, logId, false)); this.statusAndLogId = new AtomicLong(composeState(status, logId, false));
this.name = name; this.name = name;
this.listener = listener;
} }
public int getId() { public int getId() {
...@@ -168,6 +183,10 @@ public class Transaction { ...@@ -168,6 +183,10 @@ public class Transaction {
} }
} }
public boolean hasChanges() {
return hasChanges(statusAndLogId.get());
}
public void setName(String name) { public void setName(String name) {
checkNotClosed(); checkNotClosed();
this.name = name; this.name = name;
...@@ -289,10 +308,12 @@ public class Transaction { ...@@ -289,10 +308,12 @@ public class Transaction {
* Commit the transaction. Afterwards, this transaction is closed. * Commit the transaction. Afterwards, this transaction is closed.
*/ */
public void commit() { public void commit() {
assert store.openTransactions.get().get(transactionId);
long state = setStatus(STATUS_COMMITTING); long state = setStatus(STATUS_COMMITTING);
long logId = Transaction.getLogId(state); long logId = Transaction.getLogId(state);
int oldStatus = Transaction.getStatus(state); boolean hasChanges = hasChanges(state);
store.commit(this, logId, oldStatus);
store.commit(this, logId, hasChanges);
} }
/** /**
...@@ -330,7 +351,7 @@ public class Transaction { ...@@ -330,7 +351,7 @@ public class Transaction {
store.rollbackTo(this, logId, 0); store.rollbackTo(this, logId, 0);
} }
} finally { } finally {
store.endTransaction(this, STATUS_ROLLED_BACK); store.endTransaction(this, true);
} }
} }
...@@ -373,7 +394,7 @@ public class Transaction { ...@@ -373,7 +394,7 @@ public class Transaction {
@Override @Override
public String toString() { public String toString() {
long state = statusAndLogId.get(); long state = statusAndLogId.get();
return transactionId + " " + STATUS_NAMES[getStatus(state)] + " " + getLogId(state); return transactionId + "(" + sequenceNum + ") " + STATUS_NAMES[getStatus(state)] + " " + getLogId(state);
} }
......
...@@ -11,6 +11,8 @@ import java.util.BitSet; ...@@ -11,6 +11,8 @@ import java.util.BitSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
...@@ -18,7 +20,6 @@ import org.h2.mvstore.MVStore; ...@@ -18,7 +20,6 @@ import org.h2.mvstore.MVStore;
import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType; import org.h2.mvstore.type.ObjectDataType;
import org.h2.util.New;
/** /**
* A store that supports concurrent MVCC read-committed transactions. * A store that supports concurrent MVCC read-committed transactions.
...@@ -62,17 +63,52 @@ public class TransactionStore { ...@@ -62,17 +63,52 @@ public class TransactionStore {
private final DataType dataType; private final DataType dataType;
private final BitSet openTransactions = new BitSet(); /**
* This BitSet is used as vacancy indicator for transaction slots in transactions[].
* It provides easy way to find first unoccupied slot, and also allows for copy-on-write
* non-blocking updates.
*/
final AtomicReference<VersionedBitSet> openTransactions = new AtomicReference<>(new VersionedBitSet());
/**
* This is intended to be the source of ultimate truth about transaction being committed.
* Once bit is set, corresponding transaction is logically committed,
* although it might be plenty of "uncommitted" entries in various maps
* and undo record are still around.
* Nevertheless, all of those should be considered by other transactions as committed.
*/
final AtomicReference<BitSet> committingTransactions = new AtomicReference<>(new BitSet());
private boolean init; private boolean init;
private int maxTransactionId = 0xffff; /**
* Soft limit on the number of concurrently opened transactions.
* Not really needed but used by some test.
*/
private int maxTransactionId = MAX_OPEN_TRANSACTIONS;
/**
* Array holding all open transaction objects.
* Position in array is "transaction id".
* VolatileReferenceArray would do the job here, but there is no such thing in Java yet
*/
private final AtomicReferenceArray<Transaction> transactions =
new AtomicReferenceArray<>(MAX_OPEN_TRANSACTIONS + 1);
/** /**
* The next id of a temporary map. * The next id of a temporary map.
*/ */
private int nextTempMapId; private int nextTempMapId;
/**
* Hard limit on the number of concurrently opened transactions
*/
// TODO: introduce constructor parameter instead of a static field, driven by URL parameter
private static final int MAX_OPEN_TRANSACTIONS = 65535;
/** /**
* Create a new transaction store. * Create a new transaction store.
* *
...@@ -113,8 +149,12 @@ public class TransactionStore { ...@@ -113,8 +149,12 @@ public class TransactionStore {
* If the transaction store is corrupt, this method can throw an exception, * If the transaction store is corrupt, this method can throw an exception,
* in which case the store can only be used for reading. * in which case the store can only be used for reading.
*/ */
public synchronized void init() { public void init() {
init = true; init(RollbackListener.NONE);
}
public synchronized void init(RollbackListener listener) {
if (!init) {
// remove all temporary maps // remove all temporary maps
for (String mapName : store.getMapNames()) { for (String mapName : store.getMapNames()) {
if (mapName.startsWith("temp.")) { if (mapName.startsWith("temp.")) {
...@@ -124,15 +164,40 @@ public class TransactionStore { ...@@ -124,15 +164,40 @@ public class TransactionStore {
} }
rwLock.writeLock().lock(); rwLock.writeLock().lock();
try { try {
if (undoLog.size() > 0) { if (!undoLog.isEmpty()) {
for (Long key : undoLog.keySet()) { Long key = undoLog.firstKey();
while (key != null) {
int transactionId = getTransactionId(key); int transactionId = getTransactionId(key);
openTransactions.set(transactionId); if (!openTransactions.get().get(transactionId)) {
Object[] data = preparedTransactions.get(transactionId);
int status;
String name;
if (data == null) {
if (undoLog.containsKey(getOperationId(transactionId, 0))) {
status = Transaction.STATUS_OPEN;
} else {
status = Transaction.STATUS_COMMITTING;
}
name = null;
} else {
status = (Integer) data[0];
name = (String) data[1];
}
long nextTxUndoKey = getOperationId(transactionId + 1, 0);
Long lastUndoKey = undoLog.lowerKey(nextTxUndoKey);
assert lastUndoKey != null;
assert getTransactionId(lastUndoKey) == transactionId;
long logId = getLogId(lastUndoKey) + 1;
registerTransaction(transactionId, status, name, logId, listener);
key = undoLog.ceilingKey(nextTxUndoKey);
}
} }
} }
} finally { } finally {
rwLock.writeLock().unlock(); rwLock.writeLock().unlock();
} }
init = true;
}
} }
/** /**
...@@ -143,6 +208,8 @@ public class TransactionStore { ...@@ -143,6 +208,8 @@ public class TransactionStore {
* @param max the maximum id * @param max the maximum id
*/ */
public void setMaxTransactionId(int max) { public void setMaxTransactionId(int max) {
DataUtils.checkArgument(max <= MAX_OPEN_TRANSACTIONS,
"Concurrent transactions limit is too high: {0}", max);
this.maxTransactionId = max; this.maxTransactionId = max;
} }
...@@ -200,32 +267,21 @@ public class TransactionStore { ...@@ -200,32 +267,21 @@ public class TransactionStore {
* @return the list of transactions (sorted by id) * @return the list of transactions (sorted by id)
*/ */
public List<Transaction> getOpenTransactions() { public List<Transaction> getOpenTransactions() {
if(!init) {
init();
}
rwLock.readLock().lock(); rwLock.readLock().lock();
try { try {
ArrayList<Transaction> list = New.arrayList(); ArrayList<Transaction> list = new ArrayList<>();
Long key = undoLog.firstKey(); int transactionId = 0;
while (key != null) { BitSet bitSet = openTransactions.get();
int transactionId = getTransactionId(key); while((transactionId = bitSet.nextSetBit(transactionId + 1)) > 0) {
key = undoLog.lowerKey(getOperationId(transactionId + 1, 0)); Transaction transaction = getTransaction(transactionId);
long logId = getLogId(key) + 1; if(transaction != null) {
Object[] data = preparedTransactions.get(transactionId); if(transaction.getStatus() != Transaction.STATUS_CLOSED) {
int status; list.add(transaction);
String name;
if (data == null) {
if (undoLog.containsKey(getOperationId(transactionId, 0))) {
status = Transaction.STATUS_OPEN;
} else {
status = Transaction.STATUS_COMMITTING;
} }
name = null;
} else {
status = (Integer) data[0];
name = (String) data[1];
} }
Transaction t = new Transaction(this, transactionId, status,
name, logId);
list.add(t);
key = undoLog.ceilingKey(getOperationId(transactionId + 1, 0));
} }
return list; return list;
} finally { } finally {
...@@ -245,25 +301,54 @@ public class TransactionStore { ...@@ -245,25 +301,54 @@ public class TransactionStore {
* *
* @return the transaction * @return the transaction
*/ */
public synchronized Transaction begin() { public Transaction begin() {
return begin(RollbackListener.NONE);
}
/**
* Begin a new transaction.
* @param listener to be notified in case of a rollback
*
* @return the transaction
*/
public Transaction begin(RollbackListener listener) {
Transaction transaction = registerTransaction(0, Transaction.STATUS_OPEN, null, 0, listener);
return transaction;
}
private Transaction registerTransaction(int txId, int status, String name, long logId,
RollbackListener listener) {
int transactionId; int transactionId;
int status; long sequenceNo;
if (!init) { boolean success;
throw DataUtils.newIllegalStateException( do {
DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE, VersionedBitSet original = openTransactions.get();
"Not initialized"); if (txId == 0) {
transactionId = original.nextClearBit(1);
} else {
transactionId = txId;
assert !original.get(transactionId);
} }
transactionId = openTransactions.nextClearBit(1);
if (transactionId > maxTransactionId) { if (transactionId > maxTransactionId) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS, DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS,
"There are {0} open transactions", "There are {0} open transactions",
transactionId - 1); transactionId - 1);
} }
openTransactions.set(transactionId); VersionedBitSet clone = original.clone();
status = Transaction.STATUS_OPEN; clone.set(transactionId);
return new Transaction(this, transactionId, status, null, 0); sequenceNo = clone.getVersion() + 1;
clone.setVersion(sequenceNo);
success = openTransactions.compareAndSet(original, clone);
} while(!success);
Transaction transaction = new Transaction(this, transactionId, sequenceNo, status, name, logId, listener);
assert transactions.get(transactionId) == null;
transactions.set(transactionId, transaction);
return transaction;
} }
/** /**
...@@ -276,6 +361,7 @@ public class TransactionStore { ...@@ -276,6 +361,7 @@ public class TransactionStore {
t.getName() != null) { t.getName() != null) {
Object[] v = { t.getStatus(), t.getName() }; Object[] v = { t.getStatus(), t.getName() };
preparedTransactions.put(t.getId(), v); preparedTransactions.put(t.getId(), v);
t.wasStored = true;
} }
} }
...@@ -348,23 +434,30 @@ public class TransactionStore { ...@@ -348,23 +434,30 @@ public class TransactionStore {
* *
* @param t the transaction * @param t the transaction
* @param maxLogId the last log id * @param maxLogId the last log id
* @param oldStatus last status * @param hasChanges true if there were updates within specified
* transaction (even fully rolled back),
* false if just data access
*/ */
void commit(Transaction t, long maxLogId, int oldStatus) { void commit(Transaction t, long maxLogId, boolean hasChanges) {
if (store.isClosed()) { if (store.isClosed()) {
return; return;
} }
int transactionId = t.transactionId;
// this is an atomic action that causes all changes
// made by this transaction, to be considered as "committed"
flipCommittingTransactionsBit(transactionId, true);
// TODO could synchronize on blocks (100 at a time or so) // TODO could synchronize on blocks (100 at a time or so)
rwLock.writeLock().lock(); rwLock.writeLock().lock();
try { try {
for (long logId = 0; logId < maxLogId; logId++) { for (long logId = 0; logId < maxLogId; logId++) {
Long undoKey = getOperationId(t.getId(), logId); Long undoKey = getOperationId(transactionId, logId);
Object[] op = undoLog.get(undoKey); Object[] op = undoLog.get(undoKey);
if (op == null) { if (op == null) {
// partially committed: load next // partially committed: load next
undoKey = undoLog.ceilingKey(undoKey); undoKey = undoLog.ceilingKey(undoKey);
if (undoKey == null || if (undoKey == null ||
getTransactionId(undoKey) != t.getId()) { getTransactionId(undoKey) != transactionId) {
break; break;
} }
logId = getLogId(undoKey) - 1; logId = getLogId(undoKey) - 1;
...@@ -391,8 +484,20 @@ public class TransactionStore { ...@@ -391,8 +484,20 @@ public class TransactionStore {
} }
} finally { } finally {
rwLock.writeLock().unlock(); rwLock.writeLock().unlock();
flipCommittingTransactionsBit(transactionId, false);
}
endTransaction(t, hasChanges);
} }
endTransaction(t, oldStatus);
private void flipCommittingTransactionsBit(int transactionId, boolean flag) {
boolean success;
do {
BitSet original = committingTransactions.get();
assert original.get(transactionId) != flag : flag ? "Double commit" : "Mysterious bit's disappearance";
BitSet clone = (BitSet) original.clone();
clone.set(transactionId, flag);
success = committingTransactions.compareAndSet(original, clone);
} while(!success);
} }
/** /**
...@@ -465,7 +570,7 @@ public class TransactionStore { ...@@ -465,7 +570,7 @@ public class TransactionStore {
* @param mapName the map name * @param mapName the map name
* @return the map * @return the map
*/ */
MVMap<Object, Integer> openTempMap(String mapName) { private MVMap<Object, Integer> openTempMap(String mapName) {
MVMap.Builder<Object, Integer> mapBuilder = MVMap.Builder<Object, Integer> mapBuilder =
new MVMap.Builder<Object, Integer>(). new MVMap.Builder<Object, Integer>().
keyType(dataType); keyType(dataType);
...@@ -473,21 +578,37 @@ public class TransactionStore { ...@@ -473,21 +578,37 @@ public class TransactionStore {
} }
/** /**
* End this transaction * End this transaction. Change status to CLOSED and vacate transaction slot.
* Will try to commit MVStore if autocommitDelay is 0 or if database is idle
* and amount of unsaved changes is sizable.
* *
* @param t the transaction * @param t the transaction
* @param oldStatus status of this transaction * @param hasChanges false for R/O tx
*/ */
synchronized void endTransaction(Transaction t, int oldStatus) { synchronized void endTransaction(Transaction t, boolean hasChanges) {
if (oldStatus == Transaction.STATUS_PREPARED) { int txId = t.transactionId;
preparedTransactions.remove(t.getId());
}
t.setStatus(Transaction.STATUS_CLOSED); t.setStatus(Transaction.STATUS_CLOSED);
openTransactions.clear(t.transactionId);
if (oldStatus == Transaction.STATUS_PREPARED || store.getAutoCommitDelay() == 0) { assert transactions.get(txId) == t : transactions.get(txId) + " != " + t;
store.tryCommit(); transactions.set(txId, null);
return;
boolean success;
do {
VersionedBitSet original = openTransactions.get();
assert original.get(txId);
VersionedBitSet clone = original.clone();
clone.clear(txId);
success = openTransactions.compareAndSet(original, clone);
} while(!success);
if (hasChanges) {
boolean wasStored = t.wasStored;
if (wasStored && !preparedTransactions.isClosed()) {
preparedTransactions.remove(txId);
} }
if (wasStored || store.getAutoCommitDelay() == 0) {
store.tryCommit();
} else {
// to avoid having to store the transaction log, // to avoid having to store the transaction log,
// if there is no open transaction, // if there is no open transaction,
// and if there have been many changes, store them now // and if there have been many changes, store them now
...@@ -500,6 +621,12 @@ public class TransactionStore { ...@@ -500,6 +621,12 @@ public class TransactionStore {
} }
} }
} }
}
}
Transaction getTransaction(int transactionId) {
return transactions.get(transactionId);
}
/** /**
* Rollback to an old savepoint. * Rollback to an old savepoint.
...@@ -530,13 +657,15 @@ public class TransactionStore { ...@@ -530,13 +657,15 @@ public class TransactionStore {
if (map != null) { if (map != null) {
Object key = op[1]; Object key = op[1];
VersionedValue oldValue = (VersionedValue) op[2]; VersionedValue oldValue = (VersionedValue) op[2];
VersionedValue currentValue;
if (oldValue == null) { if (oldValue == null) {
// this transaction added the value // this transaction added the value
map.remove(key); currentValue = map.remove(key);
} else { } else {
// this transaction updated the value // this transaction updated the value
map.put(key, oldValue); currentValue = map.put(key, oldValue);
} }
t.listener.onRollback(map, key, currentValue, oldValue);
} }
undoLog.remove(undoKey); undoLog.remove(undoKey);
} }
...@@ -561,22 +690,19 @@ public class TransactionStore { ...@@ -561,22 +690,19 @@ public class TransactionStore {
private long logId = maxLogId - 1; private long logId = maxLogId - 1;
private Change current; private Change current;
{
fetchNext();
}
private void fetchNext() { private void fetchNext() {
rwLock.writeLock().lock(); rwLock.writeLock().lock();
try { try {
int transactionId = t.getId();
while (logId >= toLogId) { while (logId >= toLogId) {
Long undoKey = getOperationId(t.getId(), logId); Long undoKey = getOperationId(transactionId, logId);
Object[] op = undoLog.get(undoKey); Object[] op = undoLog.get(undoKey);
logId--; logId--;
if (op == null) { if (op == null) {
// partially rolled back: load previous // partially rolled back: load previous
undoKey = undoLog.floorKey(undoKey); undoKey = undoLog.floorKey(undoKey);
if (undoKey == null || if (undoKey == null ||
getTransactionId(undoKey) != t.getId()) { getTransactionId(undoKey) != transactionId) {
break; break;
} }
logId = getLogId(undoKey); logId = getLogId(undoKey);
...@@ -584,15 +710,9 @@ public class TransactionStore { ...@@ -584,15 +710,9 @@ public class TransactionStore {
} }
int mapId = ((Integer) op[0]).intValue(); int mapId = ((Integer) op[0]).intValue();
MVMap<Object, VersionedValue> m = openMap(mapId); MVMap<Object, VersionedValue> m = openMap(mapId);
if (m == null) { if (m != null) { // could be null if map was removed later on
// map was removed later on
} else {
current = new Change();
current.mapName = m.getName();
current.key = op[1];
VersionedValue oldValue = (VersionedValue) op[2]; VersionedValue oldValue = (VersionedValue) op[2];
current.value = oldValue == null ? current = new Change(m.getName(), op[1], oldValue == null ? null : oldValue.value);
null : oldValue.value;
return; return;
} }
} }
...@@ -604,16 +724,19 @@ public class TransactionStore { ...@@ -604,16 +724,19 @@ public class TransactionStore {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
if(current == null) {
fetchNext();
}
return current != null; return current != null;
} }
@Override @Override
public Change next() { public Change next() {
if (current == null) { if(!hasNext()) {
throw DataUtils.newUnsupportedOperationException("no data"); throw DataUtils.newUnsupportedOperationException("no data");
} }
Change result = current; Change result = current;
fetchNext(); current = null;
return result; return result;
} }
...@@ -633,19 +756,53 @@ public class TransactionStore { ...@@ -633,19 +756,53 @@ public class TransactionStore {
/** /**
* The name of the map where the change occurred. * The name of the map where the change occurred.
*/ */
public String mapName; public final String mapName;
/** /**
* The key. * The key.
*/ */
public Object key; public final Object key;
/** /**
* The value. * The value.
*/ */
public Object value; public final Object value;
public Change(String mapName, Object key, Object value) {
this.mapName = mapName;
this.key = key;
this.value = value;
}
}
/**
* This listener can be registered with the transaction to be notified of
* every compensating change during transaction rollback.
* Normally this is not required, if no external resources were modified,
* because state of all transactional maps will be restored automatically.
* Only state of external resources, possibly modified by triggers
* need to be restored.
*/
public interface RollbackListener {
RollbackListener NONE = new RollbackListener() {
@Override
public void onRollback(MVMap<Object, VersionedValue> map, Object key,
VersionedValue existingValue, VersionedValue restoredValue) {
// do nothing
} }
};
/**
* Notified of a single map change (add/update/remove)
* @param map modified
* @param key of the modified entry
* @param existingValue value in the map (null if delete is rolled back)
* @param restoredValue value to be restored (null if add is rolled back)
*/
void onRollback(MVMap<Object,VersionedValue> map, Object key,
VersionedValue existingValue, VersionedValue restoredValue);
}
/** /**
* A data type that contains an array of objects with the specified data * A data type that contains an array of objects with the specified data
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore.tx;
import java.util.BitSet;
/**
* Class VersionedBitSet extends standard BitSet to add a version field.
* This will allow bit set and version to be changed atomically.
*/
final class VersionedBitSet extends BitSet
{
private long version;
public VersionedBitSet() {}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public VersionedBitSet clone() {
return (VersionedBitSet)super.clone();
}
}
...@@ -24,7 +24,7 @@ public class VersionedValue { ...@@ -24,7 +24,7 @@ public class VersionedValue {
/** /**
* The value. * The value.
*/ */
final Object value; public final Object value;
VersionedValue(long operationId, Object value) { VersionedValue(long operationId, Object value) {
this.operationId = operationId; this.operationId = operationId;
......
...@@ -621,8 +621,12 @@ public abstract class Table extends SchemaObjectBase { ...@@ -621,8 +621,12 @@ public abstract class Table extends SchemaObjectBase {
} }
} }
public Row createRow(Value[] data, int memory) {
return database.createRow(data, memory);
}
public Row getTemplateRow() { public Row getTemplateRow() {
return database.createRow(new Value[columns.length], Row.MEMORY_CALCULATE); return createRow(new Value[columns.length], Row.MEMORY_CALCULATE);
} }
/** /**
......
...@@ -107,7 +107,7 @@ public class TestMVStoreTool extends TestBase { ...@@ -107,7 +107,7 @@ public class TestMVStoreTool extends TestBase {
assertEquals(size2, FileUtils.size(fileNameNew)); assertEquals(size2, FileUtils.size(fileNameNew));
MVStoreTool.compact(fileNameCompressed, true); MVStoreTool.compact(fileNameCompressed, true);
assertEquals(size3, FileUtils.size(fileNameCompressed)); assertEquals(size3, FileUtils.size(fileNameCompressed));
trace("Recompacted in " + (System.currentTimeMillis() - start) + " ms."); trace("Re-compacted in " + (System.currentTimeMillis() - start) + " ms.");
start = System.currentTimeMillis(); start = System.currentTimeMillis();
MVStore s1 = new MVStore.Builder(). MVStore s1 = new MVStore.Builder().
......
...@@ -17,7 +17,7 @@ agent agentlib agg aggregate aggregated aggregates aggregating aggressive agile ...@@ -17,7 +17,7 @@ agent agentlib agg aggregate aggregated aggregates aggregating aggressive agile
agrave agree agreeable agreed agreement agreements agrees ahead agrave agree agreeable agreed agreement agreements agrees ahead
ahilmnqbjkcdeopfrsg aid air ajax alan alarm ale alefsym alert alessio alexander alfki ahilmnqbjkcdeopfrsg aid air ajax alan alarm ale alefsym alert alessio alexander alfki
algo algorithm algorithms alias aliased aliases aliasing align aligned alignment algo algorithm algorithms alias aliased aliases aliasing align aligned alignment
alive all allclasses alleged alleging allocate allocated allocates allocating alive all allclasses alleged alleging alloc allocate allocated allocates allocating
allocation allow allowed allowing allows almost aload alone along alpha allocation allow allowed allowing allows almost aload alone along alpha
alphabetical alphabetically already also alt alter altering alternate alternative alphabetical alphabetically already also alt alter altering alternate alternative
alternatives alters although always ambiguity ambiguous america among amount amp alternatives alters although always ambiguity ambiguous america among amount amp
...@@ -108,11 +108,12 @@ combo combobox come comes coming comma command commands commas comment commented ...@@ -108,11 +108,12 @@ combo combobox come comes coming comma command commands commas comment commented
comments commercial commit commits committed committing common commonly commons comments commercial commit commits committed committing common commonly commons
communicates communication community comp compact compacted compacting compaction communicates communication community comp compact compacted compacting compaction
compacts companies company comparable comparative comparator compare compared compacts companies company comparable comparative comparator compare compared
compares comparing comparison comparisons compatibility compatible compensation compares comparing comparison comparisons compatibility compatible
compilable compilation compile compiled compiler compiles compiling complete compensation compensating compilable compilation compile compiled
completed completely completion complex complexity compliance compliant compiler compiles compiling complete completed completely
completion complex complexity compliance compliant
complicate complicated complies comply complying component components composed complicate complicated complies comply complying component components composed
composite compound compounds compress compressed compresses compressibility compose composite compound compounds compress compressed compresses compressibility
compressible compressing compression compressor compromise compsci computation compressible compressing compression compressor compromise compsci computation
compute computed computer computers computing con concat concatenate concatenated compute computed computer computers computing con concat concatenate concatenated
concatenates concatenating concatenation concentrate concept concerning concrete concatenates concatenating concatenation concentrate concept concerning concrete
...@@ -187,8 +188,8 @@ differs dig digest digit digital digits diligence dim dimension dimensional ...@@ -187,8 +188,8 @@ differs dig digest digit digital digits diligence dim dimension dimensional
dimensions dimitrijs dinamica dining dip dips dir direct direction directly dimensions dimitrijs dinamica dining dip dips dir direct direction directly
directories directory directs dirname dirs dirty disable disabled directories directory directs dirname dirs dirty disable disabled
disablelastaccess disables disabling disadvantage disadvantages disallow disablelastaccess disables disabling disadvantage disadvantages disallow
disallowed disappear disappeared disc disclaimed disclaimer disclaimers disclaims disallowed disappear disappearance disappeared disc disclaimed disclaimer disclaimers
disclosed disconnect disconnected disconnecting disconnections disconnects disclaims disclosed disconnect disconnected disconnecting disconnections disconnects
discontinue discount discriminator discussion disjunctive disk disks dispatch discontinue discount discriminator discussion disjunctive disk disks dispatch
dispatcher display displayed displaying displays dispose disposed disposition dispatcher display displayed displaying displays dispose disposed disposition
disputes dist distance distinct distinguish distinguishable distinguished disputes dist distance distinct distinguish distinguishable distinguished
...@@ -212,16 +213,16 @@ effort egrave eid eing eins einstellung either elapsed eldest elect electronic ...@@ -212,16 +213,16 @@ effort egrave eid eing eins einstellung either elapsed eldest elect electronic
element elements elephant elig eligible eliminate elisabetta ell ellipsis elm else element elements elephant elig eligible eliminate elisabetta ell ellipsis elm else
elsewhere elton email emails embedded embedding embeds emergency emf emit emitted elsewhere elton email emails embedded embedding embeds emergency emf emit emitted
emma empire employee empty emsp emulate emulated emulates emulation enable emma empire employee empty emsp emulate emulated emulates emulation enable
enabled enables enabling enc encapsulates enclose enclosed enclosing encode enabled enables enabling enc encapsulate encapsulates enclose enclosed enclosing
encoded encoder encodes encoding encountered encounters encrypt encrypted encode encoded encoder encodes encoding encountered encounters encrypt encrypted
encrypting encryption encrypts end ended enderbury endif ending endings endless encrypting encryption encrypts end ended enderbury endif ending endings endless
endlessly endorse ends enforce enforceability enforceable enforced engine engines endlessly endorse ends enforce enforceability enforceable enforced engine engines
english enhance enhanced enhancement enhancer enlarge enough enqueued ensp ensure english enhance enhanced enhancement enhancer enlarge enough enqueued ensp ensure
ensures ensuring enter entered entering enterprise entire entities entity entrance ensures ensuring enter entered entering enterprise entire entities entity entrance
entries entry enum enumerate enumerated enumerator enumerators enumeration env envelope entries entry enum enumerate enumerated enumerator enumerators enumeration env envelope
environment environments enwiki eof eol epl epoch epoll epsilon equal equality equally environment environments enwiki eof eol epl epoch epoll epsilon equal equality equally
equals equipment equitable equiv equivalent equivalents era erable eremainder eric equals equipment equitable equiv equivalence equivalent equivalents era erable eremainder
erik err error errorlevel errors erwan ery esc escape escaped escapes escaping eric erik err error errorlevel errors erwan ery esc escape escaped escapes escaping
escargots ese espa essential essentials established estimate estimated estimates escargots ese espa essential essentials established estimate estimated estimates
estimating estimation estoppel eta etc eth etl euml euro europe europeu euros eva eval estimating estimation estoppel eta etc eth etl euml euro europe europeu euros eva eval
evaluatable evaluate evaluated evaluates evaluating evaluation evdokimov even evenly evaluatable evaluate evaluated evaluates evaluating evaluation evdokimov even evenly
...@@ -301,8 +302,8 @@ ideas identical identification identified identifier identifiers identify identi ...@@ -301,8 +302,8 @@ ideas identical identification identified identifier identifiers identify identi
identities identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq identities identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq
ifexists ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored ifexists ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored
ignoredriverprivileges ignorelist ignores ignoring ignite igrave iinc ikura ikvm ikvmc ignoredriverprivileges ignorelist ignores ignoring ignite igrave iinc ikura ikvm ikvmc
illegal iload image imageio images imaginary img iml immediately immutable imola imp illegal illegally iload image imageio images imaginary img iml immediately immutable
impact imperial impersonate impl imple implement implementation implementations imola imp impact imperial impersonate impl imple implement implementation implementations
implemented implementing implements implication implicit implicitly implied implemented implementing implements implication implicit implicitly implied
implies import important imported importing imports impose imposes impossible implies import important imported importing imports impose imposes impossible
improperly improve improved improvement improvements improves improving imul improperly improve improved improvement improvements improves improving imul
...@@ -373,8 +374,8 @@ literal literals litigation little live lives ljava llc lload lmul lneg lnot loa ...@@ -373,8 +374,8 @@ literal literals litigation little live lives ljava llc lload lmul lneg lnot loa
loaded loader loading loads lob lobs local localdb locale locales localhost loaded loader loading loads lob lobs local localdb locale locales localhost
locality localization localized localname locals locate located locates location locality localization localized localname locals locate located locates location
locations locators lock locked locker locking locks log logback logged logger locations locators lock locked locker locking locks log logback logged logger
logging logic logical login logins logo logos logout logs logsize long longblob logging logic logical logically login logins logo logos logout logs logsize long
longer longest longitude longnvarchar longs longtext longvarbinary longvarchar longblob longer longest longitude longnvarchar longs longtext longvarbinary longvarchar
look lookahead looking looks lookup lookups lookupswitch loop loopback looping look lookahead looking looks lookup lookups lookupswitch loop loopback looping
loops loose lor lore lose losing loss losses lossless losslessly lost lot lots loops loose lor lore lose losing loss losses lossless losslessly lost lot lots
low lowast lower lowercase lowercased lowest loz lpad lrem lreturn lrm lru lsaquo low lowast lower lowercase lowercased lowest loz lpad lrem lreturn lrm lru lsaquo
...@@ -412,20 +413,20 @@ mpl msg mssql mssqlserver msxml much mueller mul multi multianewarray multipart ...@@ -412,20 +413,20 @@ mpl msg mssql mssqlserver msxml much mueller mul multi multianewarray multipart
multiple multiples multiplication multiplied multiply multiplying multithreaded multiple multiples multiplication multiplied multiply multiplying multithreaded
multithreading multiuser music must mutable mutate mutation mutationtest muttered multithreading multiuser music must mutable mutate mutation mutationtest muttered
mutton mutually mvc mvcc mvn mvr mvstore mydb myna myself mysql mysqladmin mysqld mutton mutually mvc mvcc mvn mvr mvstore mydb myna myself mysql mysqladmin mysqld
mystery mystic myydd nabla naive naked name namecnt named names namespace naming mysterious mystery mystic myydd nabla naive naked name namecnt named names namespace
nan nano nanos nanosecond nanoseconds nantes napping national nations native naming nan nano nanos nanosecond nanoseconds nantes napping national nations native
natural nature naur nav navigable navigate navigation navigator nbsp ncgc nchar natural nature naur nav navigable navigate navigation navigator nbsp ncgc nchar
nclob ncr ndash near nearest nearly necessarily necessary nederlands need needed nclob ncr ndash near nearest nearly necessarily necessary nederlands need needed
needing needs neg negate negated negating negation negative negligence needing needs neg negate negated negating negation negative negligence
negotiations neighbor neither nelson neo nest nested nesterov nesting net negotiations neighbor neither nelson neo nest nested nesterov nesting net
netbeans netherlands netscape netstat network networked networks never new netbeans netherlands netscape netstat network networked networks never nevertheless
newarray newer newest newline newlines newly news newsfeed newsfeeds newsgroups new newarray newer newest newline newlines newly news newsfeed newsfeeds newsgroups
newsletter next nextval nfontes nger nice nicer nicolas night nih niklas nikolaj newsletter next nextval nfontes nger nice nicer nicolas night nih niklas nikolaj
niku nine nio nls nlst noah nobody nobuffer nocache nocheck nocycle nodata nodded niku nine nio nls nlst noah nobody nobuffer nocache nocheck nocycle nodata nodded
node nodelay nodes noel noframe noframes noindex noise nomaxvalue nominvalue non node nodelay nodes noel noframe noframes noindex noinspection noise nomaxvalue
nonce noncompliance none noop nop nopack nopasswords nopmd nor noresize normal nominvalue non nonce noncompliance none noop nop nopack nopasswords nopmd nor
normalize normalized normally northern northwoods norway nosettings not nota noresize normal normalize normalized normally northern northwoods norway nosettings
notably notation notch note notes nothing notice notices notification notified not nota notably notation notch note notes nothing notice notices notification notified
notifies notify notifying notin notranslate notwithstanding nougat nov novelist notifies notify notifying notin notranslate notwithstanding nougat nov novelist
november now nowait nowrap npl nsi nsis nsub ntext ntfs nth ntilde nucleus nul november now nowait nowrap npl nsi nsis nsub ntext ntfs nth ntilde nucleus nul
null nullable nullid nullif nulls nullsoft num number numbering numbers numeral null nullable nullid nullif nulls nullsoft num number numbering numbers numeral
...@@ -471,7 +472,7 @@ petra pfgrc pfister pgdn pgup phane phantom phase phi philip philippe ...@@ -471,7 +472,7 @@ petra pfgrc pfister pgdn pgup phane phantom phase phi philip philippe
philosophers phone php phrase phrases phromros physical pick picked pickle pico philosophers phone php phrase phrases phromros physical pick picked pickle pico
pid pieces pier pietrzak pilot piman ping pinned pipe piped pit pitest piv pivot pid pieces pier pietrzak pilot piman ping pinned pipe piped pit pitest piv pivot
pkcolumn pkcs pktable place placed placeholders places placing plain plaintext pkcolumn pkcs pktable place placed placeholders places placing plain plaintext
plan planned planner planning plans plant platform platforms play player please plan planned planner planning plans plant plenty platform platforms play player please
plug pluggable plugin plugins plus plusmn png point pointbase pointed pointer pointers plug pluggable plugin plugins plus plusmn png point pointbase pointed pointer pointers
pointing points poker poland polar pole poleposition policies policy polish poll pointing points poker poland polar pole poleposition policies policy polish poll
polling polski poly polygon pom pondered poodle pool poolable pooled pooling polling polski poly polygon pom pondered poodle pool poolable pooled pooling
...@@ -658,18 +659,18 @@ tools toolset top topic topics toplink topology tort total totals touch toward ...@@ -658,18 +659,18 @@ tools toolset top topic topics toplink topology tort total totals touch toward
tpc trace traces tracing track tracked tracker tracking tracks trade trademark tpc trace traces tracing track tracked tracker tracking tracks trade trademark
trademarks traditional traditionally trailing train trans transact transaction trademarks traditional traditionally trailing train trans transact transaction
transactional transactionally transactions transfer transferred transferring transactional transactionally transactions transfer transferred transferring
transform transformation transient transiently transition transitional transform transformation transient transiently transition transitional transitioned
transitions translatable translate translated translates translating translation transitions translatable translate translated translates translating translation
translations translator transmission transmitted transparent transport travel translations translator transmission transmitted transparent transport travel
traversal traverse traversing tray tread treat treated treatment trede tree trees traversal traverse traversing tray tread treat treated treatment trede tree trees
trial trick tricky tried tries trig trigger triggered triggers trigonometric trim trial trick tricky tried tries trig trigger triggered triggers trigonometric trim
trimmed trims trip trivial trouble true trunc truncate truncated truncates trimmed trims trip trivial trouble true trunc truncate truncated truncates
truncating truncation trunk trust trusted trx try trying tsi tsmsys tsv tucc truncating truncation trunk trust trusted truth trx try trying tsi tsmsys tsv tucc
tucker tuesday tune tunes tuning turkel turkish turn turned turns tutorial tweak tucker tuesday tune tunes tuning turkel turkish turn turned turns tutorial tweak
tweaking tweet twelve twice twitter two txt tymczak type typed typeof types typesafe tweaking tweet twelve twice twitter two txt tymczak type typed typeof types typesafe
typical typically typing typlen typname typo typos typtypmod tzd tzh tzm tzr typical typically typing typlen typname typo typos typtypmod tzd tzh tzm tzr
uacute uarr ubuntu ucase ucb ucirc ucs udt udts uffff ugly ugrave uid uint ujint uacute uarr ubuntu ucase ucb ucirc ucs udt udts uffff ugly ugrave uid uint ujint
ujlong ulimit uml umlaut umr unable unaligned unary unavailability unbound ujlong ulimit ultimate uml umlaut umr unable unaligned unary unavailability unbound
uncached uncaught unchanged unchecked uncle unclear unclosed uncommitted uncommon uncached uncaught unchanged unchecked uncle unclear unclosed uncommitted uncommon
uncompressed undefined under underflow undergraduate underline underlined uncompressed undefined under underflow undergraduate underline underlined
underlying underneath underscore understand understanding understands understood underlying underneath underscore understand understanding understands understood
...@@ -679,7 +680,7 @@ unindexed uninitialized uninstall uninteresting uninterpreted uninterruptible ...@@ -679,7 +680,7 @@ unindexed uninitialized uninstall uninteresting uninterpreted uninterruptible
union unique uniquely uniqueness uniques unit united units universal universally union unique uniquely uniqueness uniques unit united units universal universally
unix unixtime unknown unless unlike unlikely unlimited unlink unlinked unload unloaded unix unixtime unknown unless unlike unlikely unlimited unlink unlinked unload unloaded
unloading unloads unlock unlocked unlocking unlocks unmaintained unmappable unloading unloads unlock unlocked unlocking unlocks unmaintained unmappable
unmapped unmodified unmounted unnamed unnecessarily unnecessary unneeded uno unmapped unmodified unmounted unnamed unnecessarily unnecessary unneeded uno unoccupied
unofficial unordered unpredictable unquoted unrecognized unrecoverable unofficial unordered unpredictable unquoted unrecognized unrecoverable
unreferenced unregister unregisters unrelated unreleased unsafe unsaved unscaled unreferenced unregister unregisters unrelated unreleased unsafe unsaved unscaled
unset unsigned unsorted unspecified unstable unsuccessful unsupported unset unsigned unsorted unspecified unstable unsuccessful unsupported
...@@ -689,7 +690,7 @@ updating upgrade upgraded upgrader upgrades upgrading upload uploaded upon upper ...@@ -689,7 +690,7 @@ updating upgrade upgraded upgrader upgrades upgrading upload uploaded upon upper
uppercase uppercased uppermost ups upsert upset upside upsih upsilon urgent urgently uppercase uppercased uppermost ups upsert upset upside upsih upsilon urgent urgently
uri url urls usa usable usage usd use used useful user userbyid username userpwd uri url urls usa usable usage usd use used useful user userbyid username userpwd
users uses using usr usual usually utc ute utf util utilities utility utilization users uses using usr usual usually utc ute utf util utilities utility utilization
utilize utilizes utils uui uuid uuml vacuum vacuuming val valid validate utilize utilizes utils uui uuid uuml vacancy vacuum vacuuming val valid validate
validated validates validating validation validities validity validly valign validated validates validating validation validities validity validly valign
valuable value values van var varargs varbinary varchar variable variables valuable value values van var varargs varbinary varchar variable variables
variance variant variants varies various varp varray vars vary varying vasilakis variance variant variants varies various varp varray vars vary varying vasilakis
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论