/* Copyright (c) 2001-2009, 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.lib.OrderedHashSet;
/**
* 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 Table table;
Row memoryRow;
int rowId;
boolean isMemory;
public static RowAction addAction(Session session, byte type, Table table,
Row row) {
RowAction action = row.rowAction;
if (action == null) {
action = new RowAction(session, table, type);
if (row.isMemory()) {
action.isMemory = true;
}
action.memoryRow = row;
action.rowId = row.getPos();
row.rowAction = action;
} else {
if (action.type == ACTION_DELETE_FINAL) {
throw Error.runtimeError(ErrorCode.U_S0500, "RowAction");
}
if (action.type == ACTION_NONE) {
action.setAsAction(session, type);
} else {
RowActionBase actionItem = action;
while (actionItem.next != null) {
actionItem = actionItem.next;
}
RowActionBase newAction = new RowActionBase(session, type);
actionItem.next = newAction;
}
}
return action;
}
/**
* Constructor. <p>
*
* @param session
* @param type type of action
*/
RowAction(Session session, Table table, byte type) {
super(session, type);
this.table = table;
}
public synchronized RowAction duplicate(int newRowId) {
RowAction action = duplicate();
action.rowId = newRowId;
return action;
}
synchronized RowAction duplicate() {
RowAction action = new RowAction(session, table, type);
action.setAsAction(this);
action.memoryRow = memoryRow;
action.rowId = rowId;
action.isMemory = isMemory;
return action;
}
synchronized void setAsAction(Session session, byte type) {
this.session = session;
this.type = type;
changeTimestamp = session.actionTimestamp;
}
synchronized void setAsAction(RowActionBase action) {
super.setAsAction(action);
}
private void setAsNoOp(Row row) {
memoryRow = null;
synchronized (row) {
row.hasAction = false;
row.rowAction = null;
}
session = null;
// actionTimestamp = 0;
commitTimestamp = 0;
// rolledback = false;
// prepared = false;
type = RowActionBase.ACTION_NONE;
next = null;
}
private void setAsDeleteFinal() {
rolledback = false;
prepared = false;
type = RowActionBase.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 void commit(Session session) {
RowActionBase action = this;
do {
if (action.session == session && action.commitTimestamp == 0) {
action.commitTimestamp = session.actionTimestamp;
action.prepared = false;
}
action = action.next;
} while (action != null);
}
/**
* 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.actionTimestamp == 0) {
action.commitTimestamp = session.actionTimestamp;
action.rolledback = true;
action.prepared = false;
}
}
action = action.next;
} while (action != null);
}
/**
* returns type of commit performed on timestamp. ACTION_NONE if none.
*/
synchronized int getCommitType(long timestamp) {
RowActionBase action = this;
int type = ACTION_NONE;
do {
if (action.commitTimestamp == timestamp) {
type = action.type;
}
action = action.next;
} while (action != null);
return type;
}
/**
* 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.isolationMode
== SessionInterface.TX_READ_COMMITTED;
action = this;
if (readCommitted) {
do {
if (action.session == session) {
// 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.rolledback || action.type == ACTION_NONE) {
action = action.next;
continue;
}
if (action.session != session) {
if (action.prepared) {
return false;
}
if (action.commitTimestamp == 0
&& action.actionTimestamp != 0) {
set.add(action.session);
} else if (action.commitTimestamp > commitTimestamp) {
commitTimestamp = action.commitTimestamp;
}
}
action = action.next;
} while (action != null);
return commitTimestamp < timestamp;
}
/**
* returns false if cannot complete
* when READ COMMITTED, false result always means repeat action and adds
* set of sessions to wait on in the parameter (may be no wait)
*/
synchronized boolean complete(Session session, OrderedHashSet set) {
RowActionBase action;
boolean readCommitted = session.isolationMode
== 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) {
if (action.actionTimestamp == 0) {
action.actionTimestamp = session.actionTimestamp;
}
} else {
if (action.prepared) {
return false;
}
if (readCommitted) {
if (action.commitTimestamp > session.actionTimestamp) {
result = false;
} else if (action.commitTimestamp == 0
&& action.actionTimestamp != 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 getLastChangeActionType(long timestamp) {
RowActionBase action = this;
int actionType = ACTION_NONE;
do {
if (action.changeTimestamp == timestamp) {
actionType = action.type;
}
action = action.next;
} while (action != null);
return actionType;
}
synchronized int getActionType(long timestamp) {
int actionType = ACTION_NONE;
RowActionBase action = this;
do {
if (action.actionTimestamp == timestamp) {
if (action.type == RowActionBase.ACTION_DELETE) {
if (actionType == RowActionBase.ACTION_INSERT) {
actionType = RowActionBase.ACTION_NONE;
action = action.next;
continue;
}
}
actionType = action.type;
}
action = action.next;
} while (action != null);
return actionType;
}
synchronized int getPos() {
return rowId;
}
synchronized void setPos(int pos) {
rowId = pos;
}
/**
* merge rolled back actions
*/
synchronized void mergeRollback(Row row) {
RowActionBase action = this;
RowActionBase head = null;
RowActionBase tail = null;
if (type == RowActionBase.ACTION_DELETE_FINAL
|| type == RowActionBase.ACTION_NONE) {
return;
}
do {
if (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) {
boolean exists = (type == RowActionBase.ACTION_DELETE);
if (exists) {
setAsNoOp(row);
} else {
setAsDeleteFinal();
}
} else {
if (head != this) {
setAsAction(head);
}
}
}
/**
* merge rolled back actions on a given timestamp
*/
synchronized boolean mergeRollback(Row row, long timestamp) {
RowActionBase action = this;
RowActionBase head = null;
RowActionBase tail = null;
if (type == RowActionBase.ACTION_DELETE_FINAL
|| type == RowActionBase.ACTION_NONE) {
return true;
}
do {
if (action.commitTimestamp == timestamp) {
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) {
boolean exists = (type == RowActionBase.ACTION_DELETE);
if (exists) {
setAsNoOp(row);
} else {
setAsDeleteFinal();
}
return exists;
} else {
if (head != this) {
setAsAction(head);
}
}
return true;
}
/**
* merge session actions committed on or before given timestamp.
*
* return false if row is to be deleted
*
*/
synchronized void mergeToTimestamp(Row row, long timestamp) {
RowActionBase action = this;
RowActionBase head = null;
RowActionBase tail = null;
boolean exists = true;
/* debug 190
if (row.rowActionB == null) {
// row.rowActionB = this.duplicate(timestamp);
} else {
RowActionBase tailB = row.rowActionB;
while (tailB.next != null) {
tailB = tailB.next;
}
// tailB.next = this.duplicate(timestamp);
}
// debug 190
*/
if (type == RowActionBase.ACTION_DELETE_FINAL
|| type == RowActionBase.ACTION_NONE) {
return;
}
do {
if (action.commitTimestamp != 0
&& action.commitTimestamp <= timestamp) {
if (tail != null) {
tail.next = null;
}
exists = (action.type == RowActionBase.ACTION_INSERT);
} else {
if (head == null) {
head = tail = action;
} else {
tail.next = action;
tail = action;
}
}
action = action.next;
} while (action != null);
if (head == null) {
if (exists) {
setAsNoOp(row);
} else {
setAsDeleteFinal();
}
} else if (head != this) {
setAsAction(head);
}
}
synchronized boolean isPriorTo(long threshold) {
RowActionBase action = this;
do {
if (action.type == ACTION_DELETE_FINAL
|| action.commitTimestamp == 0
|| action.commitTimestamp > threshold) {
return false;
}
action = action.next;
} while (action != null);
return true;
}
synchronized boolean canRead(Session session) {
long threshold;
boolean canRead = true;
if (type == RowActionBase.ACTION_DELETE_FINAL) {
return false;
} else if (type == RowActionBase.ACTION_NONE) {
return true;
}
canRead = type == RowActionBase.ACTION_DELETE;
RowActionBase action = this;
if (session == null) {
threshold = Long.MAX_VALUE;
} else {
switch (session.isolationMode) {
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.rolledback) {
action = action.next;
continue;
}
if (session == action.session) {
if (action.type == RowActionBase.ACTION_DELETE) {
canRead = false;
} else if (action.type == RowActionBase.ACTION_INSERT) {
canRead = true;
}
action = action.next;
continue;
} else if (action.commitTimestamp == 0) {
action = action.next;
continue;
}
if (action.commitTimestamp < threshold) {
if (action.type == RowActionBase.ACTION_DELETE) {
canRead = false;
} else if (action.type == RowActionBase.ACTION_INSERT) {
canRead = true;
}
}
action = action.next;
continue;
} while (action != null);
return canRead;
}
}