blob: 02000d6209fdfb6468e23d4b89a272e5732379c9 [file] [log] [blame]
/* Copyright (c) 2001-2010, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.persist.PersistentStore;
/**
* Represents the chain of insert / delete / rollback / commit actions on a row.
*
* @author Fred Toussi (fredt@users dot sourceforge dot net)
* @version 2.0.0
* @since 2.0.0
*/
public class RowAction extends RowActionBase {
//
final TableBase table;
final PersistentStore store;
Row memoryRow;
int rowId;
boolean isMemory;
RowAction updatedAction;
public static RowAction addInsertAction(Session session, TableBase table,
Row row) {
RowAction action = new RowAction(session, table, ACTION_INSERT, row,
null);
row.rowAction = action;
return action;
}
public static RowAction addDeleteAction(Session session, TableBase table,
Row row, int[] colMap) {
RowAction action = row.rowAction;
if (action == null) {
action = new RowAction(session, table, ACTION_DELETE, row, colMap);
row.rowAction = action;
return action;
}
return action.addDeleteAction(session, colMap);
}
public static boolean addRefAction(Session session, Row row,
int[] colMap) {
RowAction action = row.rowAction;
if (action == null) {
action = new RowAction(session, row.getTable(), ACTION_REF, row,
colMap);
row.rowAction = action;
return true;
}
return action.addRefAction(session, colMap);
}
public RowAction(Session session, TableBase table, byte type, Row row,
int[] colMap) {
this.session = session;
this.type = type;
this.actionTimestamp = session.actionTimestamp;
this.table = table;
this.store = session.sessionData.getRowStore(table);
this.isMemory = row.isMemory();
this.memoryRow = row;
this.rowId = row.getPos();
this.changeColumnMap = colMap;
}
private RowAction(RowAction other) {
this.session = other.session;
this.type = other.type;
this.actionTimestamp = other.actionTimestamp;
this.table = other.table;
this.store = other.store;
this.isMemory = other.isMemory;
this.memoryRow = other.memoryRow;
this.rowId = other.rowId;
this.changeColumnMap = other.changeColumnMap;
}
synchronized public int getType() {
return type;
}
synchronized RowAction addDeleteAction(Session session, int[] colMap) {
if (type == ACTION_NONE) {
setAsAction(session, ACTION_DELETE);
changeColumnMap = colMap;
} else {
RowActionBase action = this;
while (true) {
if (action.type == ACTION_INSERT) {
if (action.commitTimestamp == 0
&& session != action.session) {
throw Error.runtimeError(ErrorCode.U_S0500,
"RowAction");
}
} else if (action.type == ACTION_DELETE) {
if (session != action.session) {
session.tempSet.add(action.session);
return null;
}
} else if (action.type == ACTION_REF) {
if (session != action.session
&& action.commitTimestamp == 0) {
if (colMap == null
|| ArrayUtil.haveCommonElement(
colMap, action.changeColumnMap)) {
session.tempSet.add(action.session);
return null;
}
}
}
if (action.next == null) {
break;
}
action = action.next;
}
RowActionBase newAction = new RowActionBase(session,
ACTION_DELETE);
newAction.changeColumnMap = colMap;
action.next = newAction;
}
return this;
}
synchronized boolean addRefAction(Session session, int[] colMap) {
if (type == ACTION_NONE) {
setAsAction(session, ACTION_REF);
changeColumnMap = colMap;
return true;
}
RowActionBase action = this;
do {
if (session == action.session) {
if (action.type == ACTION_REF
&& action.changeColumnMap == colMap
&& action.commitTimestamp == 0) {
return false;
}
if (action.type == ACTION_INSERT) {
if (action.commitTimestamp == 0) {
return false;
}
}
} else {
if (action.type == ACTION_DELETE
&& action.commitTimestamp == 0) {
if (action.changeColumnMap == null
|| ArrayUtil.haveCommonElement(
colMap, action.changeColumnMap)) {
session.tempSet.add(action.session);
return false;
}
}
}
if (action.next == null) {
break;
}
action = action.next;
} while (true);
RowActionBase newAction = new RowActionBase(session, ACTION_REF);
newAction.changeColumnMap = colMap;
action.next = newAction;
return true;
}
public boolean checkDeleteActions() {
return false;
}
public synchronized RowAction duplicate(Row newRow) {
RowAction action = new RowAction(session, table, type, newRow,
changeColumnMap);
return action;
}
synchronized void setAsAction(Session session, byte type) {
this.session = session;
this.type = type;
actionTimestamp = session.actionTimestamp;
changeColumnMap = null;
}
synchronized void setAsAction(RowActionBase action) {
super.setAsAction(action);
}
public void setAsNoOp() {
// memoryRow = null;
session = null;
actionTimestamp = 0;
commitTimestamp = 0;
rolledback = false;
deleteComplete = false;
changeColumnMap = null;
prepared = false;
type = ACTION_NONE;
next = null;
}
private void setAsDeleteFinal(long timestamp) {
actionTimestamp = 0;
commitTimestamp = timestamp;
rolledback = false;
deleteComplete = false;
prepared = false;
changeColumnMap = null;
type = ACTION_DELETE_FINAL;
next = null;
}
/** for two-phased pre-commit */
synchronized void prepareCommit(Session session) {
RowActionBase action = this;
do {
if (action.session == session && action.commitTimestamp == 0) {
action.prepared = true;
}
action = action.next;
} while (action != null);
}
synchronized int commit(Session session) {
RowActionBase action = this;
int actiontype = ACTION_NONE;
do {
if (action.session == session && action.commitTimestamp == 0) {
action.commitTimestamp = session.actionTimestamp;
action.prepared = false;
if (action.type == ACTION_INSERT) {
actiontype = action.type;
} else if (action.type == ACTION_DELETE) {
if (actiontype == ACTION_INSERT) {
// ACTION_INSERT + ACTION_DELETE
actiontype = ACTION_INSERT_DELETE;
} else {
actiontype = action.type;
}
}
}
action = action.next;
} while (action != null);
return actiontype;
}
public boolean isDeleted() {
RowActionBase action = this;
do {
if (action.commitTimestamp != 0) {
if (action.type == ACTION_DELETE
|| action.type == ACTION_DELETE_FINAL) {
return true;
}
}
action = action.next;
} while (action != null);
return false;
}
/**
* returns type of commit performed on timestamp. ACTION_NONE if none.
* assumes rolled-back actions have already been merged
*/
synchronized int getCommitTypeOn(long timestamp) {
RowActionBase action = this;
int actionType = ACTION_NONE;
do {
if (action.commitTimestamp == timestamp) {
if (action.type == ACTION_INSERT) {
actionType = action.type;
} else if (action.type == ACTION_DELETE) {
if (actionType == ACTION_INSERT) {
// ACTION_INSERT + ACTION_DELETE
actionType = ACTION_INSERT_DELETE;
} else {
actionType = action.type;
}
}
}
action = action.next;
} while (action != null);
return actionType;
}
/**
* returns false if another committed session has altered the same row
*/
synchronized boolean canCommit(Session session, OrderedHashSet set) {
RowActionBase action;
long timestamp = session.transactionTimestamp;
long commitTimestamp = 0;
final boolean readCommitted = session.isolationLevel
== SessionInterface.TX_READ_COMMITTED;
boolean hasDelete = false;
action = this;
if (readCommitted) {
do {
if (action.session == session
&& action.type == ACTION_DELETE) {
// for READ_COMMITTED, use action timestamp for later conflicts
if (action.commitTimestamp == 0) {
timestamp = action.actionTimestamp;
}
}
action = action.next;
} while (action != null);
action = this;
}
do {
if (action.session == session) {
if (action.type == ACTION_DELETE) {
hasDelete = true;
}
} else {
if (action.rolledback || action.type != ACTION_DELETE) {
action = action.next;
continue;
}
if (action.prepared) {
return false;
}
if (action.commitTimestamp == 0) {
set.add(action.session);
} else if (action.commitTimestamp > commitTimestamp) {
commitTimestamp = action.commitTimestamp;
}
}
action = action.next;
} while (action != null);
if (!hasDelete) {
return true;
}
return commitTimestamp < timestamp;
}
synchronized void complete(Session session) {
RowActionBase action;
action = this;
do {
if (action.session == session) {
if (action.actionTimestamp == 0) {
action.actionTimestamp = session.actionTimestamp;
}
}
action = action.next;
} while (action != null);
}
/**
* returns false if cannot complete
* when READ COMMITTED, false result always means repeat action and adds
* to set parameter the sessions to wait on (may be no wait)
*/
synchronized boolean complete(Session session, OrderedHashSet set) {
RowActionBase action;
boolean readCommitted = session.isolationLevel
== SessionInterface.TX_READ_COMMITTED;
boolean result = true;
action = this;
do {
if (action.rolledback || action.type == ACTION_NONE) {
action = action.next;
continue;
}
if (action.session == session) {
//
} else {
if (action.prepared) {
set.add(action.session);
return false;
}
if (readCommitted) {
if (action.commitTimestamp > session.actionTimestamp) {
// 2.0 -- investigate
// can redo - if deletes
// can redo - if dup, but will likely fail at retry
// can redo - if ref, but will likely fail at retry
set.add(session);
result = false;
} else if (action.commitTimestamp == 0) {
set.add(action.session);
result = false;
}
} else if (action.commitTimestamp
> session.transactionTimestamp) {
return false;
}
}
action = action.next;
} while (action != null);
return result;
}
synchronized int getActionType(long timestamp) {
int actionType = ACTION_NONE;
RowActionBase action = this;
do {
if (action.actionTimestamp == timestamp) {
if (action.type == ACTION_DELETE) {
if (actionType == ACTION_INSERT) {
actionType = ACTION_INSERT_DELETE;
} else {
actionType = action.type;
}
} else if (action.type == ACTION_INSERT) {
actionType = action.type;
}
}
action = action.next;
} while (action != null);
return actionType;
}
public synchronized int getPos() {
return rowId;
}
synchronized void setPos(int pos) {
rowId = pos;
}
private int getRollbackType(Session session) {
int actionType = ACTION_NONE;
RowActionBase action = this;
do {
if (action.session == session && action.rolledback) {
if (action.type == ACTION_DELETE) {
if (actionType == ACTION_INSERT) {
actionType = ACTION_INSERT_DELETE;
} else {
actionType = action.type;
}
} else if (action.type == ACTION_INSERT) {
actionType = action.type;
}
}
action = action.next;
} while (action != null);
return actionType;
}
/**
* Rollback actions for a session including and after the given timestamp
*/
synchronized void rollback(Session session, long timestamp) {
RowActionBase action = this;
do {
if (action.session == session && action.commitTimestamp == 0) {
if (action.actionTimestamp >= timestamp) {
action.commitTimestamp = session.actionTimestamp;
action.rolledback = true;
action.prepared = false;
}
}
action = action.next;
} while (action != null);
}
/**
* merge rolled back actions
*/
synchronized int mergeRollback(Session session, long timestamp, Row row) {
RowActionBase action = this;
RowActionBase head = null;
RowActionBase tail = null;
int rollbackAction = getRollbackType(session);
do {
if (action.session == session && action.rolledback) {
if (tail != null) {
tail.next = null;
}
} else {
if (head == null) {
head = tail = action;
} else {
tail.next = action;
tail = action;
}
}
action = action.next;
} while (action != null);
if (head == null) {
switch (rollbackAction) {
case ACTION_INSERT :
case ACTION_INSERT_DELETE :
setAsDeleteFinal(timestamp);
break;
case ACTION_DELETE :
case ACTION_NONE :
default :
setAsNoOp();
break;
}
} else {
if (head != this) {
setAsAction(head);
}
}
return rollbackAction;
}
/**
* merge session actions committed on given timestamp.
*
* may be called more than once on same action
*
*/
synchronized void mergeToTimestamp(long timestamp) {
RowActionBase action = this;
RowActionBase head = null;
RowActionBase tail = null;
int commitType = getCommitTypeOn(timestamp);
if (type == ACTION_DELETE_FINAL || type == ACTION_NONE) {
return;
}
if (commitType == ACTION_DELETE
|| commitType == ACTION_INSERT_DELETE) {
setAsDeleteFinal(timestamp);
return;
}
do {
boolean expired = false;;
if (action.commitTimestamp != 0) {
if (action.commitTimestamp <= timestamp) {
expired = true;
} else if (action.type == ACTION_REF) {
expired = true;
}
}
if (expired) {
if (tail != null) {
tail.next = null;
}
} else {
if (head == null) {
head = tail = action;
} else {
tail.next = action;
tail = action;
}
}
action = action.next;
} while (action != null);
if (head == null) {
switch (commitType) {
case ACTION_DELETE :
case ACTION_INSERT_DELETE :
setAsDeleteFinal(timestamp);
break;
case ACTION_NONE :
case ACTION_INSERT :
default :
setAsNoOp();
break;
}
} else if (head != this) {
setAsAction(head);
}
mergeExpiredRefActions();
}
synchronized boolean canRead(Session session, int mode) {
long threshold;
int actionType = ACTION_NONE;
if (type == ACTION_DELETE_FINAL) {
return false;
}
if (type == ACTION_NONE) {
return true;
}
RowActionBase action = this;
if (session == null) {
threshold = Long.MAX_VALUE;
} else {
switch (session.isolationLevel) {
case SessionInterface.TX_READ_UNCOMMITTED :
threshold = Long.MAX_VALUE;
break;
case SessionInterface.TX_READ_COMMITTED :
threshold = session.actionTimestamp;
break;
case SessionInterface.TX_REPEATABLE_READ :
case SessionInterface.TX_SERIALIZABLE :
default :
threshold = session.transactionTimestamp;
break;
}
}
do {
if (action.type == ACTION_REF) {
action = action.next;
continue;
}
if (action.rolledback) {
if (action.type == ACTION_INSERT) {
actionType = ACTION_DELETE;
}
action = action.next;
continue;
}
if (session == action.session) {
if (action.type == ACTION_DELETE) {
actionType = action.type;
} else if (action.type == ACTION_INSERT) {
actionType = action.type;
}
action = action.next;
continue;
} else if (action.commitTimestamp == 0) {
if (action.type == ACTION_NONE) {
throw Error.runtimeError(ErrorCode.U_S0500, "RowAction");
} else if (action.type == ACTION_INSERT) {
if (mode == TransactionManager.ACTION_READ) {
actionType = action.ACTION_DELETE;
} else if (mode == TransactionManager.ACTION_DUP) {
if (action.changeColumnMap == null) {
actionType = ACTION_INSERT;
} else {
actionType = ACTION_DELETE;
}
} else if (mode == TransactionManager.ACTION_REF) {
actionType = ACTION_DELETE;
}
break;
} else if (action.type == ACTION_DELETE) {
if (mode == TransactionManager.ACTION_DUP) {
//
} else if (mode == TransactionManager.ACTION_REF) {
actionType = ACTION_DELETE;
}
}
action = action.next;
continue;
} else if (action.commitTimestamp < threshold) {
if (action.type == ACTION_DELETE) {
if (actionType == ACTION_INSERT) {
actionType = action.type;
} else {
actionType = action.type;
}
} else if (action.type == ACTION_INSERT) {
actionType = action.type;
}
} else {
if (action.type == ACTION_INSERT) {
actionType = ACTION_DELETE;
}
}
action = action.next;
continue;
} while (action != null);
if (actionType == ACTION_NONE || actionType == ACTION_INSERT) {
return true;
}
return false;
}
public boolean hasCurrentRefAction() {
RowActionBase action = this;
do {
if (action.type == ACTION_REF && action.commitTimestamp == 0) {
return true;
}
action = action.next;
} while (action != null);
return false;
}
/** eliminate all expired updatedAction in chain */
private RowAction mergeExpiredRefActions() {
if (updatedAction != null) {
updatedAction = updatedAction.mergeExpiredRefActions();
}
if (hasCurrentRefAction()) {
return this;
}
return updatedAction;
}
synchronized String dump() {
StringBuilder sb = new StringBuilder();
RowActionBase action = this;
do {
if (action == this && memoryRow != null) {
sb.append("" + memoryRow.getId());
}
sb.append(" " + action.session.getId() + ' ' + action.type + ' '
+ action.actionTimestamp + ' ' + action.commitTimestamp);
if (action.commitTimestamp != 0) {
if (action.rolledback) {
sb.append("r");
} else {
sb.append("c");
}
}
sb.append(" - ");
action = action.next;
} while (action != null);
return sb.toString();
}
}