/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.server; import com.foundationdb.server.error.TransactionInProgressException; import com.foundationdb.server.error.TransactionReadOnlyException; import com.foundationdb.server.service.session.Session; import com.foundationdb.server.service.transaction.TransactionService; import com.foundationdb.sql.parser.IsolationLevel; import java.util.Date; public class ServerTransaction { public static enum PeriodicallyCommit { /** The system commits when you call commit **/ OFF, /** The system commits periodically maintaining the same transaction **/ ON, /** * The system commits and closes the user-level transaction requiring the client to begin a new transaction. * For jdbc, and probably other drivers, it will create the new transaction automatically for the user. */ USERLEVEL; public static PeriodicallyCommit fromProperty(String name) { if (name == null) return OFF; return valueOf(PeriodicallyCommit.class, name.toUpperCase()); } } private final Session session; private final TransactionService txnService; private boolean readOnly, anyWrites; private IsolationLevel isolationLevel; private PeriodicallyCommit periodicallyCommit; private Date transactionTime; /** Begin a new transaction or signal an exception. */ public ServerTransaction(ServerSession server, boolean readOnly, IsolationLevel isolationLevel, PeriodicallyCommit periodicallyCommit) { this.session = server.getSession(); this.txnService = server.getTransactionService(); txnService.beginTransaction(session); this.isolationLevel = txnService.setIsolationLevel(session, isolationLevel); this.readOnly = readOnly || txnService.isolationLevelRequiresReadOnly(session, false); this.periodicallyCommit = periodicallyCommit; } public boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } public IsolationLevel getIsolationLevel() { return isolationLevel; } public IsolationLevel setIsolationLevel(IsolationLevel level) { level = txnService.setIsolationLevel(session, level); this.isolationLevel = level; // Selecting snapshot after update commits right away, like DDL. if (txnService.isolationLevelRequiresReadOnly(session, anyWrites)) { this.readOnly = true; this.anyWrites = false; } return level; } public PeriodicallyCommit getPeriodicallyCommit() { return periodicallyCommit; } public void setPeriodicallyCommit(PeriodicallyCommit periodicallyCommit) { this.periodicallyCommit = periodicallyCommit; } public void checkTransactionMode(ServerStatement.TransactionMode transactionMode) { switch (transactionMode) { case NONE: case NEW: case NEW_WRITE: throw new TransactionInProgressException(); case WRITE: if (readOnly) throw new TransactionReadOnlyException(); beforeUpdate(); break; case IMPLICIT_COMMIT: throw new IllegalArgumentException(transactionMode + " must be handled externally"); } } public void beforeUpdate() { anyWrites = true; } public void afterUpdate() { txnService.checkStatementConstraints(session); } /** Commit transaction. */ public void commit() { txnService.commitTransaction(session); } /** Rollback transaction. */ public void rollback() { txnService.rollbackTransaction(session); } /** Abort transaction that still exists on exit. */ public void abort() { txnService.rollbackTransactionIfOpen(session); } public boolean isRollbackPending() { return txnService.isRollbackPending(session); } public boolean shouldPeriodicallyCommit() { return txnService.shouldPeriodicallyCommit(session); } public void checkPeriodicallyCommit() { // USER_LEVEL is handled higher up if (periodicallyCommit == PeriodicallyCommit.ON) { txnService.periodicallyCommit(session); } } /** Return the transaction's time, which is fixed the first time * something asks for it. */ public Date getTime(ServerSession server) { if (transactionTime == null) transactionTime = server.currentTime(); return transactionTime; } }