| /* |
| |
| Derby - Class org.apache.derby.impl.sql.conn.GenericStatementContext |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.sql.conn; |
| |
| import org.apache.derby.iapi.services.context.Context; |
| |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| |
| import org.apache.derby.iapi.services.monitor.Monitor; |
| |
| import org.apache.derby.iapi.services.timer.TimerFactory; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| |
| import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
| import org.apache.derby.iapi.sql.conn.StatementContext; |
| import org.apache.derby.iapi.sql.conn.SQLSessionContext; |
| |
| import org.apache.derby.iapi.sql.depend.Dependency; |
| import org.apache.derby.iapi.sql.depend.DependencyManager; |
| |
| import org.apache.derby.iapi.sql.execute.NoPutResultSet; |
| |
| import org.apache.derby.iapi.sql.Activation; |
| import org.apache.derby.iapi.sql.ResultSet; |
| import org.apache.derby.iapi.sql.ParameterValueSet; |
| |
| import org.apache.derby.iapi.services.context.ContextImpl; |
| |
| import org.apache.derby.iapi.error.ExceptionSeverity; |
| import org.apache.derby.iapi.reference.SQLState; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.TimerTask; |
| |
| /** |
| * GenericStatementContext is pushed/popped around a statement prepare and execute |
| * so that any statement specific clean up can be performed. |
| * |
| * |
| */ |
| final class GenericStatementContext |
| extends ContextImpl implements StatementContext |
| { |
| private boolean setSavePoint; |
| private String internalSavePointName; |
| private ResultSet topResultSet; |
| private ArrayList<Dependency> dependencies; |
| private NoPutResultSet[] subqueryTrackingArray; |
| private NoPutResultSet[] materializedSubqueries; |
| private final LanguageConnectionContext lcc; |
| private boolean inUse = true; |
| |
| // This flag satisfies all the conditions |
| // for using volatile instead of synchronized. |
| // (Source: Doug Lea, Concurrent Programming in Java, Second Edition, |
| // section 2.2.7.4, page 97) |
| // true if statement has been cancelled |
| private volatile boolean cancellationFlag = false; |
| |
| // Reference to the TimerTask that will time out this statement. |
| // Needed for stopping the task when execution completes before timeout. |
| private CancelQueryTask cancelTask = null; |
| |
| private boolean parentInTrigger; // whetherparent started with a trigger on stack |
| private boolean isForReadOnly = false; |
| private boolean isAtomic; |
| private boolean isSystemCode; |
| private boolean rollbackParentContext; |
| private boolean statementWasInvalidated; |
| private String stmtText; |
| private ParameterValueSet pvs; |
| |
| /** |
| Set to one of RoutineAliasInfo.{MODIFIES_SQL_DATA, READS_SQL_DATA, CONTAINS_SQL, NO_SQL} |
| */ |
| private short sqlAllowed = -1; |
| |
| /** |
| * The activation associated with this context, or null |
| */ |
| private Activation activation; |
| |
| /** |
| * The SQLSessionContext associated with a statement context. |
| */ |
| private SQLSessionContext sqlSessionContext; |
| |
| /* |
| constructor |
| @param tc transaction |
| */ |
| GenericStatementContext(LanguageConnectionContext lcc) |
| { |
| super(lcc.getContextManager(), org.apache.derby.iapi.reference.ContextId.LANG_STATEMENT); |
| this.lcc = lcc; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT((lcc != null), |
| "Failed to get language connection context"); |
| } |
| |
| internalSavePointName = lcc.getUniqueSavepointName(); |
| } |
| |
| /** |
| * This is a TimerTask that is responsible for timing out statements, |
| * typically when an application has called Statement.setQueryTimeout(). |
| * |
| * When the application invokes execute() on a statement object, or |
| * fetches data on a ResultSet, a StatementContext object is allocated |
| * for the duration of the execution in the engine (until control is |
| * returned to the application). |
| * |
| * When the StatementContext object is assigned with setInUse(), |
| * a CancelQueryTask is scheduled if a timeout > 0 has been set. |
| */ |
| private static class CancelQueryTask |
| extends |
| TimerTask |
| { |
| /** |
| * Reference to the StatementContext for the executing statement |
| * which might time out. |
| */ |
| private StatementContext statementContext; |
| |
| /** |
| * Initializes a new task for timing out a statement's execution. |
| * This does not schedule it for execution, the caller is |
| * responsible for calling Timer.schedule() with this object |
| * as parameter. |
| */ |
| public CancelQueryTask(StatementContext ctx) |
| { |
| statementContext = ctx; |
| } |
| |
| /** |
| * Invoked by a Timer class to cancel an executing statement. |
| * This method just sets a volatile flag in the associated |
| * StatementContext object by calling StatementContext.cancel(); |
| * it is the responsibility of the thread executing the statement |
| * to check this flag regularly. |
| */ |
| public void run() |
| { |
| synchronized (this) { |
| if (statementContext != null) { |
| statementContext.cancel(); |
| } |
| } |
| } |
| |
| /** |
| * Stops this task and prevents it from cancelling a statement. |
| * Guarantees that after this method returns, the associated |
| * StatementContext object will not be tampered with by this task. |
| * Thus, the StatementContext object may safely be allocated to |
| * other executing statements. |
| */ |
| public void forgetContext() { |
| synchronized (this) { |
| statementContext = null; |
| } |
| getTimerFactory().cancel(this); |
| } |
| } |
| |
| private static TimerFactory getTimerFactory() { |
| return GenericLanguageConnectionFactory.getMonitor().getTimerFactory(); |
| } |
| |
| // StatementContext Interface |
| |
| public void setInUse |
| ( |
| boolean parentInTrigger, |
| boolean isAtomic, |
| boolean isForReadOnly, |
| String stmtText, |
| ParameterValueSet pvs, |
| long timeoutMillis |
| ) |
| { |
| inUse = true; |
| |
| this.parentInTrigger = parentInTrigger; |
| this.isForReadOnly = isForReadOnly; |
| this.isAtomic = isAtomic; |
| this.stmtText = stmtText; |
| this.pvs = pvs; |
| rollbackParentContext = false; |
| if (timeoutMillis > 0) { |
| cancelTask = new CancelQueryTask(this); |
| getTimerFactory().schedule(cancelTask, timeoutMillis); |
| } |
| } |
| |
| public void clearInUse() { |
| /* We must clear out the current top ResultSet to prepare for |
| * reusing a StatementContext. |
| */ |
| stuffTopResultSet( null, null ); |
| inUse = false; |
| |
| parentInTrigger = false; |
| isAtomic = false; |
| isForReadOnly = false; |
| this.stmtText = null; |
| sqlAllowed = -1; |
| isSystemCode = false; |
| rollbackParentContext = false; |
| statementWasInvalidated = false; |
| |
| if (cancelTask != null) { |
| cancelTask.forgetContext(); |
| cancelTask = null; |
| } |
| cancellationFlag = false; |
| activation = null; |
| sqlSessionContext = null; |
| } |
| |
| /** |
| * @see StatementContext#setSavePoint |
| * @exception StandardException Thrown on error |
| */ |
| public void setSavePoint() throws StandardException { |
| |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("traceSavepoints")) |
| { |
| SanityManager.DEBUG_PRINT( |
| "GenericStatementContext.setSavePoint()", |
| internalSavePointName); |
| } |
| } |
| |
| pleaseBeOnStack(); |
| |
| |
| lcc.getTransactionExecute().setSavePoint(internalSavePointName, null); |
| setSavePoint = true; |
| } |
| |
| /** |
| * Resets the savepoint to the current spot if it is |
| * set, otherwise, noop. Used when a commit is |
| * done on a nested connection. |
| * |
| * @see StatementContext#resetSavePoint |
| * @exception StandardException Thrown on error |
| */ |
| public void resetSavePoint() throws StandardException { |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("traceSavepoints")) |
| { |
| SanityManager.DEBUG_PRINT( |
| "GenericStatementContext.resetSavePoint()", |
| internalSavePointName); |
| } |
| } |
| |
| if (inUse && setSavePoint) |
| { |
| // RESOLVE PLUGIN ???. For the plugin, there will be no transaction controller |
| lcc.getTransactionExecute().setSavePoint(internalSavePointName, null); |
| // stage buffer management |
| } |
| } |
| |
| /** |
| * @see StatementContext#clearSavePoint |
| * @exception StandardException Thrown on error |
| */ |
| public void clearSavePoint() throws StandardException { |
| |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("traceSavepoints")) |
| { |
| SanityManager.DEBUG_PRINT("GenericStatementContext.clearSavePoint()", |
| internalSavePointName); |
| } |
| } |
| |
| pleaseBeOnStack(); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(setSavePoint, "setSavePoint is expected to be true"); |
| } |
| |
| // RESOLVE PLUGIN ???. For the plugin, there will be no transaction controller |
| lcc.getTransactionExecute().releaseSavePoint(internalSavePointName, null); |
| setSavePoint = false; |
| } |
| |
| /** |
| * Set the top ResultSet in the ResultSet tree for close down on |
| * an error. |
| * |
| * @exception StandardException thrown on error. |
| */ |
| public void setTopResultSet(ResultSet topResultSet, |
| NoPutResultSet[] subqueryTrackingArray) |
| throws StandardException |
| { |
| pleaseBeOnStack(); |
| |
| /* We have to handle both materialize and non-materialized subqueries. |
| * Materialized subqueries are attached before the top result set is |
| * set. If there are any, then we must copy them into the new |
| * subqueryTrackingArray. |
| */ |
| if (materializedSubqueries != null) |
| { |
| // Do the merging into the passed in array. |
| if (subqueryTrackingArray != null) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (this.materializedSubqueries.length != subqueryTrackingArray.length) |
| { |
| SanityManager.THROWASSERT( |
| "this.ms.length (" + this.materializedSubqueries.length + |
| ") expected to = sta.length(" + subqueryTrackingArray.length + |
| ")"); |
| } |
| } |
| for (int index = 0; index < subqueryTrackingArray.length; index++) |
| { |
| if (this.materializedSubqueries[index] != null) |
| { |
| subqueryTrackingArray[index] = this.materializedSubqueries[index]; |
| } |
| } |
| } |
| else |
| { |
| subqueryTrackingArray = this.materializedSubqueries; |
| } |
| materializedSubqueries = null; |
| } |
| |
| stuffTopResultSet( topResultSet, subqueryTrackingArray ); |
| } |
| |
| /** |
| * Private minion of setTopResultSet() and clearInUse() |
| * |
| * @param topResultSet make this the top result set |
| * @param subqueryTrackingArray where to keep track of subqueries in this statement |
| */ |
| private void stuffTopResultSet(ResultSet topResultSet, |
| NoPutResultSet[] subqueryTrackingArray) |
| { |
| this.topResultSet = topResultSet; |
| this.subqueryTrackingArray = subqueryTrackingArray; |
| dependencies = null; |
| } |
| |
| |
| /** |
| * Set the appropriate entry in the subquery tracking array for |
| * the specified subquery. |
| * Useful for closing down open subqueries on an exception. |
| * |
| * @param subqueryNumber The subquery # for this subquery |
| * @param subqueryResultSet The ResultSet at the top of the subquery |
| * @param numSubqueries The total # of subqueries in the entire query |
| * |
| * @exception StandardException thrown on error. |
| */ |
| public void setSubqueryResultSet(int subqueryNumber, |
| NoPutResultSet subqueryResultSet, |
| int numSubqueries) |
| throws StandardException |
| { |
| pleaseBeOnStack(); |
| |
| /* NOTE: In degenerate cases, it is possible that there is no top |
| * result set. For example: |
| * call (select 1 from systables).valueOf('111'); |
| * In that case, we allocate our own subquery tracking array on |
| * each call. (Gross!) |
| * (Trust me, this is only done in degenerate cases. The tests passed, |
| * except for the degenerate cases, before making this change, so we |
| * know that the top result set and array reuse is working for |
| * the non-degenerate cases.) |
| */ |
| if (subqueryTrackingArray == null) |
| { |
| if (topResultSet == null) |
| { |
| subqueryTrackingArray = new NoPutResultSet[numSubqueries]; |
| materializedSubqueries = new NoPutResultSet[numSubqueries]; |
| } |
| else |
| { |
| subqueryTrackingArray = |
| topResultSet.getSubqueryTrackingArray(numSubqueries); |
| } |
| } |
| subqueryTrackingArray[subqueryNumber] = subqueryResultSet; |
| if (materializedSubqueries != null) |
| { |
| materializedSubqueries[subqueryNumber] = subqueryResultSet; |
| } |
| } |
| |
| /** |
| * Get the subquery tracking array for this query. |
| * (Useful for runtime statistics.) |
| * |
| * @return NoPutResultSet[] The (sparse) array of tops of subquery ResultSet trees |
| * @exception StandardException thrown on error. |
| */ |
| public NoPutResultSet[] getSubqueryTrackingArray() |
| throws StandardException |
| { |
| pleaseBeOnStack(); |
| |
| return subqueryTrackingArray; |
| } |
| |
| /** |
| * Track a Dependency within this StatementContext. |
| * (We need to clear any dependencies added within this |
| * context on an error. |
| * |
| * @param dy The dependency to track. |
| * |
| * @exception StandardException thrown on error. |
| */ |
| public void addDependency(Dependency dy) |
| throws StandardException |
| { |
| pleaseBeOnStack(); |
| |
| if (dependencies == null) |
| { |
| dependencies = new ArrayList<Dependency>(); |
| } |
| dependencies.add(dy); |
| } |
| |
| /** |
| * Returns whether we started from within the context of a trigger |
| * or not. |
| * |
| * @return true if we are in a trigger context |
| */ |
| public boolean inTrigger() |
| { |
| return parentInTrigger; |
| } |
| |
| // |
| // Context interface |
| // |
| /** |
| * Close down the top ResultSet, if relevant, and rollback to the |
| * internal savepoint, if one was set. |
| * |
| * @exception StandardException thrown on error. REVISIT: don't want |
| * cleanupOnError's to throw exceptions. |
| */ |
| public void cleanupOnError(Throwable error) throws StandardException |
| { |
| |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("traceSavepoints")) |
| { |
| SanityManager.DEBUG_PRINT( |
| "GenericStatementContext.cleanupOnError()", |
| String.valueOf( hashCode() ) ); |
| } |
| } |
| |
| try { |
| /* |
| ** If it isn't a StandardException, then assume |
| ** session severity. It is probably an unexpected |
| ** java error somewhere in the language. |
| ** Store layer treats JVM error as session severity, |
| ** hence to be consistent and to avoid getting rawstore |
| ** protocol violation errors, we treat java errors here |
| ** to be of session severity. |
| */ |
| int severity = ExceptionSeverity.SESSION_SEVERITY; |
| if (error instanceof StandardException) { |
| StandardException se = (StandardException)error; |
| // Update the severity. |
| severity = se.getSeverity(); |
| // DERBY-4849: Remember that the plan was invalidated, such that |
| // we can avoid performing certain actions more than once |
| // (for correctness, not optimization). |
| if (SQLState.LANG_STATEMENT_NEEDS_RECOMPILE.equals( |
| se.getMessageId())) { |
| statementWasInvalidated = true; |
| } |
| } |
| |
| |
| /** |
| * Don't clean up this statement context if it's not in use. |
| * This can happen if you get an error while calling one of |
| * the JDBC getxxxx() methods on a ResultSet, since no statement |
| * context is pushed when those calls occur. |
| */ |
| if (! inUse) |
| { |
| return; |
| } |
| |
| /* Clean up the ResultSet, if one exists */ |
| if (topResultSet != null) |
| { |
| topResultSet.cleanUp(); |
| } |
| |
| /* Close down any open subqueries */ |
| if (subqueryTrackingArray != null) |
| { |
| for (int index = 0; index < subqueryTrackingArray.length; index++) |
| { |
| /* Remember, the array is sparse, so only check |
| * non-null entries. |
| */ |
| if (subqueryTrackingArray[index] != null) |
| { |
| subqueryTrackingArray[index].cleanUp(); |
| } |
| } |
| } |
| |
| /* Clean up any dependencies */ |
| if (dependencies != null) |
| { |
| DependencyManager dmgr = lcc.getDataDictionary().getDependencyManager(); |
| |
| for (Iterator<Dependency> iterator = dependencies.iterator(); iterator.hasNext(); ) |
| { |
| Dependency dy = iterator.next(); |
| dmgr.clearInMemoryDependency(dy); |
| } |
| |
| dependencies = null; |
| } |
| |
| if (severity <= ExceptionSeverity.STATEMENT_SEVERITY |
| && setSavePoint) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("traceSavepoints")) |
| { |
| SanityManager.DEBUG_PRINT( |
| "GenericStatementContext.cleanupOnError", |
| "rolling back to: " + internalSavePointName); |
| } |
| } |
| |
| lcc.internalRollbackToSavepoint( internalSavePointName, false, null); |
| |
| clearSavePoint(); |
| } |
| |
| if (severity >= ExceptionSeverity.TRANSACTION_SEVERITY ) |
| { |
| // transaction severity errors roll back the transaction. |
| |
| /* |
| ** We call clearSavePoint() above only for statement errors. |
| ** We don't call clearSavePoint() for transaction errors because |
| ** the savepoint will be rolled back anyway. So in this case, |
| ** we need to indicate that the savepoint is not set. |
| */ |
| setSavePoint = false; |
| } |
| |
| /* Pop the context */ |
| lcc.popStatementContext(this, error); |
| } catch(Exception ex) { |
| //DERBY-6722(GenericStatementContext.cleanupOnError() |
| //needs protection from later errors during statement |
| //cleanup |
| ex.initCause(error); |
| throw StandardException.unexpectedUserException(ex); |
| } |
| } |
| |
| /** |
| * @see Context#isLastHandler |
| */ |
| public boolean isLastHandler(int severity) |
| { |
| // For JVM errors, severity gets mapped to |
| // ExceptionSeverity.NO_APPLICABLE_SEVERITY |
| // in ContextManager.cleanupOnError. It is necessary to |
| // let outer contexts take corrective action for jvm errors, so |
| // return false as this will not be the last handler for such |
| // errors. |
| return inUse && !rollbackParentContext && |
| ( severity == ExceptionSeverity.STATEMENT_SEVERITY ); |
| } |
| |
| /** |
| * Reports whether this StatementContext is on the context stack. |
| * |
| * @return true if this StatementContext is on the context stack. false otherwise. |
| */ |
| public boolean onStack() { return inUse; } |
| |
| /** |
| * Indicates whether the statement needs to be executed atomically |
| * or not, i.e., whether a commit/rollback is permitted by a |
| * connection nested in this statement. |
| * |
| * @return true if needs to be atomic |
| */ |
| public boolean isAtomic() |
| { |
| return isAtomic; |
| } |
| |
| /** |
| * Return the text of the current statement. |
| * Note that this may be null. It is currently |
| * not set up correctly for ResultSets that aren't |
| * single row result sets (e.g SELECT), replication, |
| * and setXXXX/getXXXX jdbc methods. |
| * |
| * @return the statement text |
| */ |
| public String getStatementText() |
| { |
| return stmtText; |
| } |
| |
| // |
| // class implementation |
| // |
| |
| /** |
| * Raise an exception if this Context is not in use, that is, on the |
| * Context Stack. |
| * |
| * @exception StandardException thrown on error. |
| */ |
| private void pleaseBeOnStack() throws StandardException |
| { |
| if ( !inUse ) { throw StandardException.newException(SQLState.LANG_DEAD_STATEMENT); } |
| } |
| |
| public boolean inUse() |
| { |
| return inUse; |
| } |
| public boolean isForReadOnly() |
| { |
| return isForReadOnly; |
| } |
| |
| /** |
| * Tests whether the statement which has allocated this StatementContext |
| * object has been cancelled. This method is typically called from the |
| * thread which is executing the statement, to test whether execution |
| * should continue or stop. |
| * |
| * @return whether the statement which has allocated this StatementContext |
| * object has been cancelled. |
| */ |
| public boolean isCancelled() |
| { |
| return cancellationFlag; |
| } |
| |
| /** |
| * Cancels the statement which has allocated this StatementContext object. |
| * This is done by setting a flag in the StatementContext object. For |
| * this to have any effect, it is the responsibility of the executing |
| * statement to check this flag regularly. |
| */ |
| public void cancel() |
| { |
| cancellationFlag = true; |
| } |
| |
| public void setSQLAllowed(short allow, boolean force) { |
| |
| // cannot override a stricter setting. |
| // -1 is no routine restriction in place |
| // 0 is least restrictive |
| // 4 is most |
| if (force || (allow > sqlAllowed)) |
| sqlAllowed = allow; |
| |
| } |
| public short getSQLAllowed() { |
| if (!inUse) |
| return org.apache.derby.catalog.types.RoutineAliasInfo.NO_SQL; |
| |
| return sqlAllowed; |
| } |
| |
| /** |
| * Indicate that, in the event of a statement-level exception, |
| * this context is NOT the last one that needs to be rolled |
| * back--rather, it is nested within some other statement |
| * context, and that other context needs to be rolled back, |
| * too. |
| */ |
| public void setParentRollback() { |
| rollbackParentContext = true; |
| } |
| |
| /** |
| Set to indicate statement is system code. |
| For example a system procedure, view, function etc. |
| */ |
| public void setSystemCode() { |
| isSystemCode = true; |
| } |
| |
| /** |
| Return true if this statement is system code. |
| */ |
| public boolean getSystemCode() { |
| return isSystemCode; |
| } |
| |
| public StringBuffer appendErrorInfo() { |
| |
| StringBuffer sb = ((ContextImpl) lcc).appendErrorInfo(); |
| if (sb != null) { |
| |
| sb.append("Failed Statement is: "); |
| |
| sb.append(getStatementText()); |
| |
| if ((pvs != null) && pvs.getParameterCount() > 0) |
| { |
| String pvsString = " with " + pvs.getParameterCount() + |
| " parameters " + pvs.toString(); |
| sb.append(pvsString); |
| } |
| } |
| return sb; |
| |
| } |
| |
| /** |
| * @see StatementContext#setActivation(Activation a) |
| */ |
| public void setActivation(Activation a) { |
| activation = a; |
| } |
| |
| /** |
| * @see StatementContext#getActivation |
| */ |
| public Activation getActivation() { |
| return activation; |
| } |
| |
| /** |
| * @see StatementContext#getSQLSessionContext |
| */ |
| public SQLSessionContext getSQLSessionContext() { |
| return sqlSessionContext; |
| } |
| |
| /** |
| * @see StatementContext#setSQLSessionContext(SQLSessionContext ctx) |
| */ |
| public void setSQLSessionContext(SQLSessionContext ctx) { |
| sqlSessionContext = ctx; |
| } |
| |
| public boolean getStatementWasInvalidated() { |
| return statementWasInvalidated; |
| } |
| } |