/*
* Copyright (C) 2006 http://www.chaidb.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
package org.chaidb.db.log;
import org.chaidb.db.KernelContext;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.index.InMemIndex;
import org.chaidb.db.index.Key;
import java.util.Hashtable;
public class MemLogManagerImpl extends MemLogManager {
static final short NORMAL = 0; //the transaction is proceeding
static final short PRE_COMMIT = 1; //the transaction have precommit
static final short ROLLBACK = 2; //the transaction is rollbacking
/* The count of transaction */
private int maxCount = DEFAULT_MAX_TXN_COUNT;
/* Save transactions log */
private Hashtable txnsLog = null;
/**
* default constructor
*/
public MemLogManagerImpl() {
txnsLog = new Hashtable();
}
/**
* Set the max transaction number
*/
public void setMaxTxnCount(int maxTxnCount) {
maxCount = maxTxnCount;
}
/**
* clear the in-memory log
*/
private void cleanMemoryLog(int txnId) {
Integer id = new Integer(txnId);
txnsLog.remove(id);
}
/**
* Add in-memory index change log
*
* @param txnId The id of transaction.
* @param mode The mode of operation.(STORE_INSERT, STORE_REPLACE, DELETE)
* @param index The operated index.
* @param key The key of the index entry to be operated.
* @param newObj The new value after operation.
* @param oldObj The old value before operation.
*/
public void addMemoryLog(int txnId, InMemIndex index, short mode, KernelContext kContext, Key key, Object newObj, Object oldObj) throws ChaiDBException {
MemLogRecord record = new MemLogRecord(index, key, mode, kContext, oldObj, newObj);
Integer id = new Integer(txnId);
Link lk = (Link) txnsLog.get(id);
if (lk == null) {
if (txnsLog.size() < maxCount) {
lk = new Link();
txnsLog.put(id, lk);
} else throw new ChaiDBException(ErrorCode.TXN_TOO_MANY_IN_MEMORY);
}
// record the in-memory log only when the status is normal
if (lk.status == NORMAL) lk.insertHead(record);
}
/**
* Flush any change in memory to B-Tree.
*
* @param txnId The id of transaction.
*/
public void preCommit(int txnId) throws ChaiDBException {
Integer id = new Integer(txnId);
Link lk = (Link) txnsLog.get(id);
// if no in-memory transaction log or the change have flush to B-Tree,return
if (lk == null || lk.status == PRE_COMMIT) return;
// apply every in-memroy change to B-Tree in order(from the tail to head)
MemLogRecord record = lk.tail;
while (record != null) {
record.getIDBIndex().memFlush(record.getMode(), record.getKernelContext(), record.getKey(), record.getNewValue());
record = record.pre;
}
lk.status = PRE_COMMIT;
}
/**
* Clear the log in memory.
* Precommit must be called before calling it.
*
* @param txnId The id of transaction.
*/
public void commit(int txnId) throws ChaiDBException {
Integer id = new Integer(txnId);
Link lk = (Link) txnsLog.get(id);
if (lk == null) return;
if (lk.status != PRE_COMMIT) throw new ChaiDBException(ErrorCode.TRANSACTION_LACK_PRECOMMIT);
cleanMemoryLog(txnId);
}
/**
* Rollback any change of in memory indexes.
*
* @param txnId The id of transaction.
*/
public void rollback(int txnId) throws ChaiDBException {
Integer id = new Integer(txnId);
Link lk = (Link) txnsLog.get(id);
// if no in-memory transaction log return
if (lk == null) return;
// set the status to rollback
lk.status = ROLLBACK;
// undo each in-memroy change in reverse order(from head to tail)
MemLogRecord record = lk.head;
while (record != null) {
record.getIDBIndex().memUndo(record.getMode(), record.getKernelContext(), record.getKey(), record.getOldValue());
record = record.next;
}
cleanMemoryLog(txnId);
}
/**
* the inner class for in-memory log link head
*/
class Link {
short status = NORMAL; //the intial status is NORMAL
MemLogRecord head = null; //the head of in-memory log list
MemLogRecord tail = null; //the tail of in-memory log list
/**
* insert the in-memory log into the head of list
*
* @param record The record of in-memory log
*/
void insertHead(MemLogRecord record) {
if (head == null) {
head = record;
tail = record;
} else {
record.next = head;
head.pre = record;
head = record;
}
}
}
}