001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.conn.GenericStatementContext
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.conn;
023:
024: import org.apache.derby.iapi.services.context.Context;
025:
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027:
028: import org.apache.derby.iapi.services.monitor.Monitor;
029:
030: import org.apache.derby.iapi.services.timer.TimerFactory;
031:
032: import org.apache.derby.iapi.error.StandardException;
033:
034: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
035: import org.apache.derby.iapi.sql.conn.StatementContext;
036:
037: import org.apache.derby.iapi.sql.depend.Dependency;
038: import org.apache.derby.iapi.sql.depend.DependencyManager;
039:
040: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
041:
042: import org.apache.derby.iapi.sql.ResultSet;
043: import org.apache.derby.iapi.sql.ParameterValueSet;
044:
045: import org.apache.derby.iapi.store.access.TransactionController;
046:
047: import org.apache.derby.iapi.services.context.ContextImpl;
048:
049: import org.apache.derby.iapi.error.ExceptionSeverity;
050: import org.apache.derby.iapi.reference.SQLState;
051: import java.util.ArrayList;
052: import java.util.Iterator;
053: import java.util.Timer;
054: import java.util.TimerTask;
055: import java.sql.SQLException;
056:
057: /**
058: * GenericStatementContext is pushed/popped around a statement prepare and execute
059: * so that any statement specific clean up can be performed.
060: *
061: * @author jerry
062: *
063: */
064: final class GenericStatementContext extends ContextImpl implements
065: StatementContext {
066: private final TransactionController tc;
067:
068: private boolean setSavePoint;
069: private String internalSavePointName;
070: private ResultSet topResultSet;
071: private ArrayList dependencies;
072: private NoPutResultSet[] subqueryTrackingArray;
073: private NoPutResultSet[] materializedSubqueries;
074: private final LanguageConnectionContext lcc;
075: private boolean inUse = true;
076:
077: // This flag satisfies all the conditions
078: // for using volatile instead of synchronized.
079: // (Source: Doug Lea, Concurrent Programming in Java, Second Edition,
080: // section 2.2.7.4, page 97)
081: // true if statement has been cancelled
082: private volatile boolean cancellationFlag = false;
083:
084: // Reference to the TimerTask that will time out this statement.
085: // Needed for stopping the task when execution completes before timeout.
086: private CancelQueryTask cancelTask = null;
087:
088: private boolean parentInTrigger; // whetherparent started with a trigger on stack
089: private boolean isForReadOnly = false;
090: private boolean isAtomic;
091: private boolean isSystemCode;
092: private boolean rollbackParentContext;
093: private String stmtText;
094: private ParameterValueSet pvs;
095:
096: /**
097: Set to one of RoutineAliasInfo.{MODIFIES_SQL_DATA, READS_SQL_DATA, CONTAINS_SQL, NO_SQL}
098: */
099: private short sqlAllowed = -1;
100:
101: /*
102: constructor
103: @param tc transaction
104: */
105: GenericStatementContext(LanguageConnectionContext lcc,
106: TransactionController tc) {
107: super (
108: lcc.getContextManager(),
109: org.apache.derby.iapi.reference.ContextId.LANG_STATEMENT);
110: this .lcc = lcc;
111: this .tc = tc;
112:
113: internalSavePointName = "ISSP" + hashCode();
114:
115: if (SanityManager.DEBUG) {
116: SanityManager.ASSERT((lcc != null),
117: "Failed to get language connection context");
118: }
119:
120: }
121:
122: /**
123: * This is a TimerTask that is responsible for timing out statements,
124: * typically when an application has called Statement.setQueryTimeout().
125: *
126: * When the application invokes execute() on a statement object, or
127: * fetches data on a ResultSet, a StatementContext object is allocated
128: * for the duration of the execution in the engine (until control is
129: * returned to the application).
130: *
131: * When the StatementContext object is assigned with setInUse(),
132: * a CancelQueryTask is scheduled if a timeout > 0 has been set.
133: */
134: private static class CancelQueryTask extends TimerTask {
135: /**
136: * Reference to the StatementContext for the executing statement
137: * which might time out.
138: */
139: private StatementContext statementContext;
140:
141: /**
142: * Initializes a new task for timing out a statement's execution.
143: * This does not schedule it for execution, the caller is
144: * responsible for calling Timer.schedule() with this object
145: * as parameter.
146: */
147: public CancelQueryTask(StatementContext ctx) {
148: statementContext = ctx;
149: }
150:
151: /**
152: * Invoked by a Timer class to cancel an executing statement.
153: * This method just sets a volatile flag in the associated
154: * StatementContext object by calling StatementContext.cancel();
155: * it is the responsibility of the thread executing the statement
156: * to check this flag regularly.
157: */
158: public void run() {
159: synchronized (this ) {
160: if (statementContext != null) {
161: statementContext.cancel();
162: }
163: }
164: }
165:
166: /**
167: * Stops this task and prevents it from cancelling a statement.
168: * Guarantees that after this method returns, the associated
169: * StatementContext object will not be tampered with by this task.
170: * Thus, the StatementContext object may safely be allocated to
171: * other executing statements.
172: */
173: public void forgetContext() {
174: boolean mayStillRun = !cancel();
175: if (mayStillRun) {
176: synchronized (this ) {
177: statementContext = null;
178: }
179: }
180: }
181: }
182:
183: // StatementContext Interface
184:
185: public void setInUse(boolean parentInTrigger, boolean isAtomic,
186: boolean isForReadOnly, String stmtText,
187: ParameterValueSet pvs, long timeoutMillis) {
188: inUse = true;
189:
190: this .parentInTrigger = parentInTrigger;
191: this .isForReadOnly = isForReadOnly;
192: this .isAtomic = isAtomic;
193: this .stmtText = stmtText;
194: this .pvs = pvs;
195: rollbackParentContext = false;
196: if (timeoutMillis > 0) {
197: TimerFactory factory = Monitor.getMonitor()
198: .getTimerFactory();
199: Timer timer = factory.getCancellationTimer();
200: cancelTask = new CancelQueryTask(this );
201: timer.schedule(cancelTask, timeoutMillis);
202: }
203: }
204:
205: public void clearInUse() {
206: /* We must clear out the current top ResultSet to prepare for
207: * reusing a StatementContext.
208: */
209: stuffTopResultSet(null, null);
210: inUse = false;
211:
212: parentInTrigger = false;
213: isAtomic = false;
214: isForReadOnly = false;
215: this .stmtText = null;
216: sqlAllowed = -1;
217: isSystemCode = false;
218: rollbackParentContext = false;
219:
220: if (cancelTask != null) {
221: cancelTask.forgetContext();
222: cancelTask = null;
223: }
224: cancellationFlag = false;
225: }
226:
227: /**
228: * @see StatementContext#setSavePoint
229: * @exception StandardException Thrown on error
230: */
231: public void setSavePoint() throws StandardException {
232:
233: if (SanityManager.DEBUG) {
234: if (SanityManager.DEBUG_ON("traceSavepoints")) {
235: SanityManager.DEBUG_PRINT(
236: "GenericStatementContext.setSavePoint()",
237: internalSavePointName);
238: }
239: }
240:
241: pleaseBeOnStack();
242:
243: // RESOLVE PLUGIN ???. For the plugin, there will be no transaction controller
244: if (tc != null) {
245: tc.setSavePoint(internalSavePointName, null);
246: }
247: setSavePoint = true;
248: }
249:
250: /**
251: * Resets the savepoint to the current spot if it is
252: * set, otherwise, noop. Used when a commit is
253: * done on a nested connection.
254: *
255: * @see StatementContext#resetSavePoint
256: * @exception StandardException Thrown on error
257: */
258: public void resetSavePoint() throws StandardException {
259: if (SanityManager.DEBUG) {
260: if (SanityManager.DEBUG_ON("traceSavepoints")) {
261: SanityManager.DEBUG_PRINT(
262: "GenericStatementContext.resetSavePoint()",
263: internalSavePointName);
264: }
265: }
266:
267: if (inUse && setSavePoint) {
268: // RESOLVE PLUGIN ???. For the plugin, there will be no transaction controller
269: if (tc != null) {
270: tc.setSavePoint(internalSavePointName, null);
271: }
272: // stage buffer management
273: }
274: }
275:
276: /**
277: * @see StatementContext#clearSavePoint
278: * @exception StandardException Thrown on error
279: */
280: public void clearSavePoint() throws StandardException {
281:
282: if (SanityManager.DEBUG) {
283: if (SanityManager.DEBUG_ON("traceSavepoints")) {
284: SanityManager.DEBUG_PRINT(
285: "GenericStatementContext.clearSavePoint()",
286: internalSavePointName);
287: }
288: }
289:
290: pleaseBeOnStack();
291:
292: if (SanityManager.DEBUG) {
293: SanityManager.ASSERT(setSavePoint,
294: "setSavePoint is expected to be true");
295: }
296:
297: // RESOLVE PLUGIN ???. For the plugin, there will be no transaction controller
298: if (tc != null) {
299: tc.releaseSavePoint(internalSavePointName, null);
300: }
301: setSavePoint = false;
302: }
303:
304: /**
305: * Set the top ResultSet in the ResultSet tree for close down on
306: * an error.
307: *
308: * @exception StandardException thrown on error.
309: */
310: public void setTopResultSet(ResultSet topResultSet,
311: NoPutResultSet[] subqueryTrackingArray)
312: throws StandardException {
313: pleaseBeOnStack();
314:
315: /* We have to handle both materialize and non-materialized subqueries.
316: * Materialized subqueries are attached before the top result set is
317: * set. If there are any, then we must copy them into the new
318: * subqueryTrackingArray.
319: */
320: if (materializedSubqueries != null) {
321: // Do the merging into the passed in array.
322: if (subqueryTrackingArray != null) {
323: if (SanityManager.DEBUG) {
324: if (this .materializedSubqueries.length != subqueryTrackingArray.length) {
325: SanityManager.THROWASSERT("this.ms.length ("
326: + this .materializedSubqueries.length
327: + ") expected to = sta.length("
328: + subqueryTrackingArray.length + ")");
329: }
330: }
331: for (int index = 0; index < subqueryTrackingArray.length; index++) {
332: if (this .subqueryTrackingArray[index] != null) {
333: subqueryTrackingArray[index] = this .materializedSubqueries[index];
334: }
335: }
336: } else {
337: subqueryTrackingArray = this .materializedSubqueries;
338: }
339: materializedSubqueries = null;
340: }
341:
342: stuffTopResultSet(topResultSet, subqueryTrackingArray);
343: }
344:
345: /**
346: * Private minion of setTopResultSet() and clearInUse()
347: *
348: * @param topResultSet make this the top result set
349: * @param subqueryTrackingArray where to keep track of subqueries in this statement
350: */
351: private void stuffTopResultSet(ResultSet topResultSet,
352: NoPutResultSet[] subqueryTrackingArray) {
353: this .topResultSet = topResultSet;
354: this .subqueryTrackingArray = subqueryTrackingArray;
355: dependencies = null;
356: }
357:
358: /**
359: * Set the appropriate entry in the subquery tracking array for
360: * the specified subquery.
361: * Useful for closing down open subqueries on an exception.
362: *
363: * @param subqueryNumber The subquery # for this subquery
364: * @param subqueryResultSet The ResultSet at the top of the subquery
365: * @param numSubqueries The total # of subqueries in the entire query
366: *
367: * @exception StandardException thrown on error.
368: */
369: public void setSubqueryResultSet(int subqueryNumber,
370: NoPutResultSet subqueryResultSet, int numSubqueries)
371: throws StandardException {
372: pleaseBeOnStack();
373:
374: /* NOTE: In degenerate cases, it is possible that there is no top
375: * result set. For example:
376: * call (select 1 from systables).valueOf('111');
377: * In that case, we allocate our own subquery tracking array on
378: * each call. (Gross!)
379: * (Trust me, this is only done in degenerate cases. The tests passed,
380: * except for the degenerate cases, before making this change, so we
381: * know that the top result set and array reuse is working for
382: * the non-degenerate cases.)
383: */
384: if (subqueryTrackingArray == null) {
385: if (topResultSet == null) {
386: subqueryTrackingArray = new NoPutResultSet[numSubqueries];
387: materializedSubqueries = new NoPutResultSet[numSubqueries];
388: } else {
389: subqueryTrackingArray = topResultSet
390: .getSubqueryTrackingArray(numSubqueries);
391: }
392: }
393: subqueryTrackingArray[subqueryNumber] = subqueryResultSet;
394: if (materializedSubqueries != null) {
395: materializedSubqueries[subqueryNumber] = subqueryResultSet;
396: }
397: }
398:
399: /**
400: * Get the subquery tracking array for this query.
401: * (Useful for runtime statistics.)
402: *
403: * @return NoPutResultSet[] The (sparse) array of tops of subquery ResultSet trees
404: * @exception StandardException thrown on error.
405: */
406: public NoPutResultSet[] getSubqueryTrackingArray()
407: throws StandardException {
408: pleaseBeOnStack();
409:
410: return subqueryTrackingArray;
411: }
412:
413: /**
414: * Track a Dependency within this StatementContext.
415: * (We need to clear any dependencies added within this
416: * context on an error.
417: *
418: * @param dy The dependency to track.
419: *
420: * @exception StandardException thrown on error.
421: */
422: public void addDependency(Dependency dy) throws StandardException {
423: pleaseBeOnStack();
424:
425: if (dependencies == null) {
426: dependencies = new ArrayList();
427: }
428: dependencies.add(dy);
429: }
430:
431: /**
432: * Returns whether we started from within the context of a trigger
433: * or not.
434: *
435: * @return true if we are in a trigger context
436: */
437: public boolean inTrigger() {
438: return parentInTrigger;
439: }
440:
441: //
442: // Context interface
443: //
444: /**
445: * Close down the top ResultSet, if relevant, and rollback to the
446: * internal savepoint, if one was set.
447: *
448: * @exception StandardException thrown on error. REVISIT: don't want
449: * cleanupOnError's to throw exceptions.
450: */
451: public void cleanupOnError(Throwable error)
452: throws StandardException {
453:
454: if (SanityManager.DEBUG) {
455: if (SanityManager.DEBUG_ON("traceSavepoints")) {
456: SanityManager.DEBUG_PRINT(
457: "GenericStatementContext.cleanupOnError()",
458: String.valueOf(hashCode()));
459: }
460: }
461:
462: /*
463: ** If it isn't a StandardException, then assume
464: ** session severity. It is probably an unexpected
465: ** java error somewhere in the language.
466: ** Store layer treats JVM error as session severity,
467: ** hence to be consistent and to avoid getting rawstore
468: ** protocol violation errors, we treat java errors here
469: ** to be of session severity.
470: */
471: int severity = (error instanceof StandardException) ? ((StandardException) error)
472: .getSeverity()
473: : ExceptionSeverity.SESSION_SEVERITY;
474:
475: /**
476: * Don't clean up this statement context if it's not in use.
477: * This can happen if you get an error while calling one of
478: * the JDBC getxxxx() methods on a ResultSet, since no statement
479: * context is pushed when those calls occur.
480: */
481: if (!inUse) {
482: return;
483: }
484:
485: /* Clean up the ResultSet, if one exists */
486: if (topResultSet != null) {
487: topResultSet.cleanUp();
488: }
489:
490: /* Close down any open subqueries */
491: if (subqueryTrackingArray != null) {
492: for (int index = 0; index < subqueryTrackingArray.length; index++) {
493: /* Remember, the array is sparse, so only check
494: * non-null entries.
495: */
496: if (subqueryTrackingArray[index] != null) {
497: subqueryTrackingArray[index].cleanUp();
498: }
499: }
500: }
501:
502: /* Clean up any dependencies */
503: if (dependencies != null) {
504: DependencyManager dmgr = lcc.getDataDictionary()
505: .getDependencyManager();
506:
507: for (Iterator iterator = dependencies.iterator(); iterator
508: .hasNext();) {
509: Dependency dy = (Dependency) iterator.next();
510: dmgr.clearInMemoryDependency(dy);
511: }
512:
513: dependencies = null;
514: }
515:
516: if (severity <= ExceptionSeverity.STATEMENT_SEVERITY
517: && setSavePoint) {
518: if (SanityManager.DEBUG) {
519: if (SanityManager.DEBUG_ON("traceSavepoints")) {
520: SanityManager
521: .DEBUG_PRINT(
522: "GenericStatementContext.cleanupOnError",
523: "rolling back to: "
524: + internalSavePointName);
525: }
526: }
527:
528: lcc.internalRollbackToSavepoint(internalSavePointName,
529: false, null);
530:
531: clearSavePoint();
532: }
533:
534: if (severity >= ExceptionSeverity.TRANSACTION_SEVERITY) {
535: // transaction severity errors roll back the transaction.
536:
537: /*
538: ** We call clearSavePoint() above only for statement errors.
539: ** We don't call clearSavePoint() for transaction errors because
540: ** the savepoint will be rolled back anyway. So in this case,
541: ** we need to indicate that the savepoint is not set.
542: */
543: setSavePoint = false;
544: }
545:
546: /* Pop the context */
547: lcc.popStatementContext(this , error);
548: }
549:
550: /**
551: * @see Context#isLastHandler
552: */
553: public boolean isLastHandler(int severity) {
554: // For JVM errors, severity gets mapped to
555: // ExceptionSeverity.NO_APPLICABLE_SEVERITY
556: // in ContextManager.cleanupOnError. It is necessary to
557: // let outer contexts take corrective action for jvm errors, so
558: // return false as this will not be the last handler for such
559: // errors.
560: return inUse && !rollbackParentContext
561: && (severity == ExceptionSeverity.STATEMENT_SEVERITY);
562: }
563:
564: /**
565: * Reports whether this StatementContext is on the context stack.
566: *
567: * @return true if this StatementContext is on the context stack. false otherwise.
568: */
569: public boolean onStack() {
570: return inUse;
571: }
572:
573: /**
574: * Indicates whether the statement needs to be executed atomically
575: * or not, i.e., whether a commit/rollback is permitted by a
576: * connection nested in this statement.
577: *
578: * @return true if needs to be atomic
579: */
580: public boolean isAtomic() {
581: return isAtomic;
582: }
583:
584: /**
585: * Return the text of the current statement.
586: * Note that this may be null. It is currently
587: * not set up correctly for ResultSets that aren't
588: * single row result sets (e.g SELECT), replication,
589: * and setXXXX/getXXXX jdbc methods.
590: *
591: * @return the statement text
592: */
593: public String getStatementText() {
594: return stmtText;
595: }
596:
597: //
598: // class implementation
599: //
600:
601: /**
602: * Raise an exception if this Context is not in use, that is, on the
603: * Context Stack.
604: *
605: * @exception StandardException thrown on error.
606: */
607: private void pleaseBeOnStack() throws StandardException {
608: if (!inUse) {
609: throw StandardException
610: .newException(SQLState.LANG_DEAD_STATEMENT);
611: }
612: }
613:
614: public boolean inUse() {
615: return inUse;
616: }
617:
618: public boolean isForReadOnly() {
619: return isForReadOnly;
620: }
621:
622: /**
623: * Tests whether the statement which has allocated this StatementContext
624: * object has been cancelled. This method is typically called from the
625: * thread which is executing the statement, to test whether execution
626: * should continue or stop.
627: *
628: * @return whether the statement which has allocated this StatementContext
629: * object has been cancelled.
630: */
631: public boolean isCancelled() {
632: return cancellationFlag;
633: }
634:
635: /**
636: * Cancels the statement which has allocated this StatementContext object.
637: * This is done by setting a flag in the StatementContext object. For
638: * this to have any effect, it is the responsibility of the executing
639: * statement to check this flag regularly.
640: */
641: public void cancel() {
642: cancellationFlag = true;
643: }
644:
645: public void setSQLAllowed(short allow, boolean force) {
646:
647: // cannot override a stricter setting.
648: // -1 is no routine restriction in place
649: // 0 is least restrictive
650: // 4 is most
651: if (force || (allow > sqlAllowed))
652: sqlAllowed = allow;
653:
654: }
655:
656: public short getSQLAllowed() {
657: if (!inUse)
658: return org.apache.derby.catalog.types.RoutineAliasInfo.NO_SQL;
659:
660: return sqlAllowed;
661: }
662:
663: /**
664: * Indicate that, in the event of a statement-level exception,
665: * this context is NOT the last one that needs to be rolled
666: * back--rather, it is nested within some other statement
667: * context, and that other context needs to be rolled back,
668: * too.
669: */
670: public void setParentRollback() {
671: rollbackParentContext = true;
672: }
673:
674: /**
675: Set to indicate statement is system code.
676: For example a system procedure, view, function etc.
677: */
678: public void setSystemCode() {
679: isSystemCode = true;
680: }
681:
682: /**
683: Return true if this statement is system code.
684: */
685: public boolean getSystemCode() {
686: return isSystemCode;
687: }
688:
689: public StringBuffer appendErrorInfo() {
690:
691: StringBuffer sb = ((ContextImpl) lcc).appendErrorInfo();
692: if (sb != null) {
693:
694: sb.append("Failed Statement is: ");
695:
696: sb.append(getStatementText());
697:
698: if (lcc.getLogStatementText() && (pvs != null)
699: && pvs.getParameterCount() > 0) {
700: String pvsString = " with " + pvs.getParameterCount()
701: + " parameters " + pvs.toString();
702: sb.append(pvsString);
703: }
704: }
705: return sb;
706:
707: }
708: }
|