database-jones/Adapter/api/Transaction.js (157 lines of code) (raw):

/* Copyright (c) 2012, 2015 Oracle and/or its affiliates. All rights reserved. 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; version 2 of the License. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ "use strict"; var udebug = unified_debug.getLogger("Transaction.js"), userContext = require("./UserContext.js"), JonesPromise = require("jones-promises"); /** Transaction is implemented as a state machine. * States are: * idle: user has not called begin * active user has called begin * rollback_only user has called begin and setRollbackOnly * * Functions that cause state transitions are: * begin: begin a transaction if not already begun * rollback: rollback the current transaction * setRollbackOnly: allow rollback but not commit * commit: commit the current transaction * rollback: roll back the current transaction * * Functions that return status are: * getRollbackOnly returns whether the state is rollback only * isActive returns whether the state is active * * There are only three valid transitions: * * (Idle) begin -> (Active) commit -> (Idle) * (Idle) begin -> (Active) rollback -> (Idle) * (Idle) begin -> (Active) setRollbackOnly -> (RollbackOnly) rollback -> (Idle) */ function Idle() { this.name = 'Idle'; } var idle = new Idle(); function Active() { this.name = 'Active'; } var active = new Active(); function RollbackOnly() { this.name = 'RollbackOnly'; } var rollbackOnly = new RollbackOnly(); /** An error may have occurred. If there is a callback defined, signal the error via the callback, * and return the new transaction state. The state should remain the same (the current state * is passed in the function). * If no callback is defined with an error, throw the error (and remain in the current state). */ var callbackErrOrThrow = function(err, user_arguments) { var promise = new JonesPromise(); // signal the error via the promise if (err) { promise.reject(err); } else { promise.fulfill(); } if (typeof(user_arguments[0]) === 'function') { var return_arguments = []; var i; for (i = 1; i < user_arguments.length; ++i) { return_arguments[i] = user_arguments[i]; } return_arguments[0] = err; user_arguments[0].apply(null, return_arguments); } return promise; }; Idle.prototype.begin = function(session, user_arguments) { udebug.log('Idle begin'); // notify dbSession if they are interested if (typeof(session.dbSession.begin) === 'function') { session.dbSession.begin(); } session.tx.setState(active); // no error return callbackErrOrThrow(null, user_arguments); }; Idle.prototype.commit = function(session, user_arguments) { udebug.log('Idle commit'); var err = new Error('Illegal state: Idle cannot commit.'); err.sqlstate = '25000'; return callbackErrOrThrow(err, user_arguments); }; Idle.prototype.rollback = function(session, user_arguments) { udebug.log('Idle rollback'); var err = new Error('Illegal state: Idle cannot rollback.'); err.sqlstate = '25000'; return callbackErrOrThrow(err, user_arguments); }; Idle.prototype.isActive = function() { if(udebug.is_detail()) {udebug.log_detail('Idle isActive');} return false; }; Idle.prototype.setRollbackOnly = function(session, user_arguments) { udebug.log('Idle setRollbackOnly'); }; Idle.prototype.getRollbackOnly = function() { udebug.log('Idle getRollbackOnly'); return false; }; Active.prototype.begin = function(session, user_arguments) { udebug.log('Active begin'); var err = new Error('Illegal state: Active cannot begin.'); err.sqlstate = '25000'; return callbackErrOrThrow(err, user_arguments); }; Active.prototype.commit = function(session, user_arguments) { udebug.log('Active commit'); var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory); // delegate to context's commit for execution which will change the state to idle return context.commit(); }; Active.prototype.rollback = function(session, user_arguments) { udebug.log('Active rollback'); var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory); // delegate to context's rollback for execution which will change the state to idle return context.rollback(); }; Active.prototype.isActive = function() { if(udebug.is_detail()) {udebug.log_detail('Active isActive');} return true; }; Active.prototype.setRollbackOnly = function(session) { udebug.log('Active setRollbackOnly'); session.tx.setState(rollbackOnly); }; Active.prototype.getRollbackOnly = function() { udebug.log('Active getRollbackOnly'); return false; }; RollbackOnly.prototype.begin = function(session, user_arguments) { udebug.log('RollbackOnly begin'); var err = new Error('Illegal state: RollbackOnly cannot begin.'); err.sqlstate = '25000'; return callbackErrOrThrow(err, user_arguments); }; RollbackOnly.prototype.commit = function(session, user_arguments) { udebug.log('RollbackOnly commit'); var err = new Error('Illegal state: RollbackOnly cannot commit.'); err.sqlstate = '25000'; return callbackErrOrThrow(err, user_arguments); }; RollbackOnly.prototype.rollback = function(session, user_arguments) { udebug.log('RollbackOnly rollback'); var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory); // delegate to context's rollback for execution which will set the state to idle return context.rollback(); }; RollbackOnly.prototype.isActive = function() { if(udebug.is_detail()) {udebug.log_detail('RollbackOnly isActive');} return true; }; RollbackOnly.prototype.setRollbackOnly = function() { udebug.log('RollbackOnly setRollbackOnly'); }; RollbackOnly.prototype.getRollbackOnly = function() { udebug.log('RollbackOnly getRollbackOnly'); return true; }; function Transaction(session) { this.session = session; this.state = idle; } // States can be addressed by name in the Transaction object for setState(newState) Transaction.prototype.idle = idle; Transaction.prototype.active = active; Transaction.prototype.rollbackOnly = rollbackOnly; Transaction.prototype.begin = function() { udebug.log('Transaction.begin'); return this.state.begin(this.session, arguments); }; Transaction.prototype.commit = function() { udebug.log('Transaction.commit'); return this.state.commit(this.session, arguments); }; Transaction.prototype.rollback = function() { udebug.log('Transaction.rollback'); return this.state.rollback(this.session, arguments); }; Transaction.prototype.isActive = function() { if(udebug.is_detail()) {udebug.log_detail('Transaction.isActive');} return this.state.isActive(); }; Transaction.prototype.setRollbackOnly = function() { udebug.log('Transaction.setRollbackOnly'); this.state.setRollbackOnly(this.session, arguments); }; Transaction.prototype.getRollbackOnly = function() { udebug.log('Transaction.getRollbackOnly'); return this.state.getRollbackOnly(); }; /** This function is used by each state to change the state of the transaction. * @param newState one of: Idle, Active, RollbackOnly */ Transaction.prototype.setState = function(newState) { udebug.log('Transaction.setState to', newState.name); this.state = newState; }; exports.Transaction = Transaction;