/* Copyright (c) 2001-2008, 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.DoubleIntIndex; import org.hsqldb.lib.HashMappedList; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.LongKeyIntValueHashMap; /** * Manages rows involved in transactions * * @author fredt@users * @version 1.8.0 * @since 1.8.0 */ public class TransactionManager { LongKeyIntValueHashMap rowSessionMap; boolean reWriteProtect; Database database; TransactionManager(Database db) { database = db; rowSessionMap = new LongKeyIntValueHashMap(true); } public void setReWriteProtection(boolean value) { reWriteProtect = value; } void checkDelete(Session session, Row row) throws HsqlException {} void checkDelete(Session session, HashMappedList rowSet) throws HsqlException { if (!reWriteProtect) { return; } int sessionid = session.getId(); for (int i = 0, size = rowSet.size(); i < size; i++) { Row row = (Row) rowSet.getKey(i); long rowid = row.getId(); if (rowSessionMap.get(rowid, sessionid) != sessionid) { throw Trace.error(Trace.INVALID_TRANSACTION_STATE_NO_SUBCLASS, Trace.ITSNS_OVERWRITE); } } } void checkDelete(Session session, HsqlArrayList rowSet) throws HsqlException { if (!reWriteProtect) { return; } int sessionid = session.getId(); for (int i = 0, size = rowSet.size(); i < size; i++) { Row row = (Row) rowSet.get(i); long rowid = row.getId(); if (rowSessionMap.get(rowid, sessionid) != sessionid) { throw Trace.error(Trace.INVALID_TRANSACTION_STATE_NO_SUBCLASS, Trace.ITSNS_OVERWRITE); } } } void commit(Session session) { Object[] list = session.rowActionList.getArray(); int size = session.rowActionList.size(); for (int i = 0; i < size; i++) { Transaction tx = (Transaction) list[i]; long rowid = tx.row.getId(); tx.commit(session); rowSessionMap.remove(rowid); } session.rowActionList.clear(); session.savepoints.clear(); } synchronized void rollback(Session session) { rollbackTransactions(session, 0, false); session.savepoints.clear(); } void rollbackSavepoint(Session session, String name) throws HsqlException { int index = session.savepoints.getIndex(name); if (index < 0) { throw Trace.error(Trace.SAVEPOINT_NOT_FOUND, name); } Integer oi = (Integer) session.savepoints.get(index); int limit = oi.intValue(); rollbackTransactions(session, limit, false); while (session.savepoints.size() > index) { session.savepoints.remove(session.savepoints.size() - 1); } } void rollbackTransactions(Session session, int limit, boolean log) { Object[] list = session.rowActionList.getArray(); int size = session.rowActionList.size(); for (int i = size - 1; i >= limit; i--) { Transaction tx = (Transaction) list[i]; tx.rollback(session, log); } for (int i = limit; i < size; i++) { Transaction tx = (Transaction) list[i]; long rowid = tx.row.getId(); rowSessionMap.remove(rowid); } session.rowActionList.setSize(limit); } void addTransaction(Session session, Transaction transaction) { if (reWriteProtect) { rowSessionMap.put(transaction.row.getId(), session.getId()); } } private long globalActionTimestamp = 0; /** * gets the next timestamp for an action */ long nextActionTimestamp() { globalActionTimestamp++; return globalActionTimestamp; } /** * Return an array of all transactions sorted by System Change No. */ Transaction[] getTransactionList() { Session[] sessions = database.sessionManager.getAllSessions(); int[] tIndex = new int[sessions.length]; Transaction[] transactions; int transactionCount = 0; { int actioncount = 0; for (int i = 0; i < sessions.length; i++) { actioncount += sessions[i].getTransactionSize(); } transactions = new Transaction[actioncount]; } while (true) { boolean found = false; long minChangeNo = Long.MAX_VALUE; int sessionIndex = 0; // find the lowest available SCN across all sessions for (int i = 0; i < sessions.length; i++) { int tSize = sessions[i].getTransactionSize(); if (tIndex[i] < tSize) { Transaction current = (Transaction) sessions[i].rowActionList.get( tIndex[i]); if (current.SCN < minChangeNo) { minChangeNo = current.SCN; sessionIndex = i; } found = true; } } if (!found) { break; } HsqlArrayList currentList = sessions[sessionIndex].rowActionList; for (; tIndex[sessionIndex] < currentList.size(); ) { Transaction current = (Transaction) currentList.get(tIndex[sessionIndex]); // if the next change no is in this session, continue adding if (current.SCN == minChangeNo + 1) { minChangeNo++; } if (current.SCN == minChangeNo) { transactions[transactionCount++] = current; tIndex[sessionIndex]++; } else { break; } } } return transactions; } /** * Return a lookup of all transactions ids for cached tables. */ public DoubleIntIndex getTransactionIDList() { Session[] sessions = database.sessionManager.getAllSessions(); DoubleIntIndex lookup = new DoubleIntIndex(10, false); lookup.setKeysSearchTarget(); for (int i = 0; i < sessions.length; i++) { HsqlArrayList tlist = sessions[i].rowActionList; for (int j = 0, size = tlist.size(); j < size; j++) { Transaction tx = (Transaction) tlist.get(j); if (tx.tTable.getTableType() == Table.CACHED_TABLE) { lookup.addUnique(tx.row.getPos(), 0); } } } return lookup; } /** * Convert row ID's for cached table rows in transactions */ public void convertTransactionIDs(DoubleIntIndex lookup) { Session[] sessions = database.sessionManager.getAllSessions(); for (int i = 0; i < sessions.length; i++) { HsqlArrayList tlist = sessions[i].rowActionList; for (int j = 0, size = tlist.size(); j < size; j++) { Transaction tx = (Transaction) tlist.get(j); if (tx.tTable.getTableType() == Table.CACHED_TABLE) { int pos = lookup.lookupFirstEqual(tx.row.getPos()); tx.row.setPos(pos); } } } } }