001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.OnceResultSet
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.execute;
023:
024: import org.apache.derby.iapi.services.monitor.Monitor;
025:
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027:
028: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
029: import org.apache.derby.iapi.services.stream.InfoStreams;
030:
031: import org.apache.derby.iapi.sql.conn.StatementContext;
032:
033: import org.apache.derby.iapi.sql.execute.ExecRow;
034: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
035:
036: import org.apache.derby.iapi.types.DataValueDescriptor;
037: import org.apache.derby.iapi.sql.Activation;
038: import org.apache.derby.iapi.sql.ResultSet;
039:
040: import org.apache.derby.iapi.services.loader.GeneratedMethod;
041:
042: import org.apache.derby.iapi.reference.SQLState;
043: import org.apache.derby.iapi.error.StandardException;
044:
045: /**
046: * Takes an expression subquery's result set and verifies that only
047: * a single scalar value is being returned.
048: * NOTE: A row with a single column containing null will be returned from
049: * getNextRow() if the underlying subquery ResultSet is empty.
050: *
051: * @author jerry
052: */
053: public class OnceResultSet extends NoPutResultSetImpl {
054: /* Statics for cardinality check */
055: public static final int DO_CARDINALITY_CHECK = 1;
056: public static final int NO_CARDINALITY_CHECK = 2;
057: public static final int UNIQUE_CARDINALITY_CHECK = 3;
058:
059: /* Used to cache row with nulls for case when subquery result set
060: * is empty.
061: */
062: private ExecRow rowWithNulls;
063:
064: /* Used to cache the StatementContext */
065: private StatementContext statementContext;
066:
067: // set in constructor and not altered during
068: // life of object.
069: public NoPutResultSet source;
070: private GeneratedMethod emptyRowFun;
071: private int cardinalityCheck;
072: public int subqueryNumber;
073: public int pointOfAttachment;
074:
075: //
076: // class interface
077: //
078: public OnceResultSet(NoPutResultSet s, Activation a,
079: GeneratedMethod emptyRowFun, int cardinalityCheck,
080: int resultSetNumber, int subqueryNumber,
081: int pointOfAttachment, double optimizerEstimatedRowCount,
082: double optimizerEstimatedCost) {
083: super (a, resultSetNumber, optimizerEstimatedRowCount,
084: optimizerEstimatedCost);
085: source = s;
086: this .emptyRowFun = emptyRowFun;
087: this .cardinalityCheck = cardinalityCheck;
088: this .subqueryNumber = subqueryNumber;
089: this .pointOfAttachment = pointOfAttachment;
090: constructorTime += getElapsedMillis(beginTime);
091: }
092:
093: //
094: // ResultSet interface (leftover from NoPutResultSet)
095: //
096:
097: /**
098: * open a scan on the table. scan parameters are evaluated
099: * at each open, so there is probably some way of altering
100: * their values...
101: *
102: * @exception StandardException thrown if cursor finished.
103: */
104: public void openCore() throws StandardException {
105: /* NOTE: We can't get code generation
106: * to generate calls to reopenCore() for
107: * subsequent probes, so we just handle
108: * it here.
109: */
110: if (isOpen) {
111: reopenCore();
112: return;
113: }
114:
115: beginTime = getCurrentTimeMillis();
116:
117: source.openCore();
118:
119: /* Notify StatementContext about ourself so that we can
120: * get closed down, if necessary, on an exception.
121: */
122: if (statementContext == null) {
123: statementContext = getLanguageConnectionContext()
124: .getStatementContext();
125: }
126: statementContext.setSubqueryResultSet(subqueryNumber, this ,
127: activation.getNumSubqueries());
128:
129: numOpens++;
130: isOpen = true;
131: openTime += getElapsedMillis(beginTime);
132: }
133:
134: /**
135: * reopen a scan on the table. scan parameters are evaluated
136: * at each open, so there is probably some way of altering
137: * their values...
138: *
139: * @exception StandardException thrown if cursor finished.
140: */
141: public void reopenCore() throws StandardException {
142: beginTime = getCurrentTimeMillis();
143: if (SanityManager.DEBUG)
144: SanityManager.ASSERT(isOpen, "OnceResultSet already open");
145:
146: source.reopenCore();
147: numOpens++;
148:
149: openTime += getElapsedMillis(beginTime);
150: }
151:
152: /**
153: * Return the requested value computed from the next row.
154: *
155: * @exception StandardException thrown on failure.
156: * StandardException ScalarSubqueryCardinalityViolation
157: * Thrown if scalar subquery returns more than 1 row.
158: */
159: public ExecRow getNextRowCore() throws StandardException {
160: ExecRow candidateRow = null;
161: ExecRow secondRow = null;
162: ExecRow result = null;
163:
164: beginTime = getCurrentTimeMillis();
165: // This is an ASSERT and not a real error because this is never
166: // outermost in the tree and so a next call when closed will not occur.
167: if (SanityManager.DEBUG)
168: SanityManager.ASSERT(isOpen, "OpenResultSet not open");
169:
170: if (isOpen) {
171: candidateRow = source.getNextRowCore();
172:
173: if (candidateRow != null) {
174: switch (cardinalityCheck) {
175: case DO_CARDINALITY_CHECK:
176: case NO_CARDINALITY_CHECK:
177: candidateRow = candidateRow.getClone();
178: if (cardinalityCheck == DO_CARDINALITY_CHECK) {
179: /* Raise an error if the subquery returns > 1 row
180: * We need to make a copy of the current candidateRow since
181: * the getNextRow() for this check will wipe out the underlying
182: * row.
183: */
184: secondRow = source.getNextRowCore();
185: if (secondRow != null) {
186: close();
187: StandardException se = StandardException
188: .newException(SQLState.LANG_SCALAR_SUBQUERY_CARDINALITY_VIOLATION);
189: throw se;
190: }
191: }
192: result = candidateRow;
193: break;
194:
195: case UNIQUE_CARDINALITY_CHECK:
196: candidateRow = candidateRow.getClone();
197: secondRow = source.getNextRowCore();
198: DataValueDescriptor orderable1 = candidateRow
199: .getColumn(1);
200: while (secondRow != null) {
201: DataValueDescriptor orderable2 = secondRow
202: .getColumn(1);
203: if (!(orderable1.compare(
204: DataValueDescriptor.ORDER_OP_EQUALS,
205: orderable2, true, true))) {
206: close();
207: StandardException se = StandardException
208: .newException(SQLState.LANG_SCALAR_SUBQUERY_CARDINALITY_VIOLATION);
209: throw se;
210: }
211: secondRow = source.getNextRowCore();
212: }
213: result = candidateRow;
214: break;
215:
216: default:
217: if (SanityManager.DEBUG) {
218: SanityManager
219: .THROWASSERT("cardinalityCheck not unexpected to be "
220: + cardinalityCheck);
221: }
222: break;
223: }
224: } else if (rowWithNulls == null) {
225: rowWithNulls = (ExecRow) emptyRowFun.invoke(activation);
226: result = rowWithNulls;
227: } else {
228: result = rowWithNulls;
229: }
230: }
231:
232: currentRow = result;
233: setCurrentRow(result);
234: rowsSeen++;
235:
236: nextTime += getElapsedMillis(beginTime);
237: return result;
238: }
239:
240: /**
241: * If the result set has been opened,
242: * close the open scan.
243: *
244: * @exception StandardException thrown on error
245: */
246: public void close() throws StandardException {
247: beginTime = getCurrentTimeMillis();
248: if (isOpen) {
249: // we don't want to keep around a pointer to the
250: // row ... so it can be thrown away.
251: // REVISIT: does this need to be in a finally
252: // block, to ensure that it is executed?
253: clearCurrentRow();
254:
255: source.close();
256:
257: super .close();
258: } else if (SanityManager.DEBUG)
259: SanityManager.DEBUG("CloseRepeatInfo",
260: "Close of OnceResultSet repeated");
261:
262: closeTime += getElapsedMillis(beginTime);
263: }
264:
265: /**
266: * @see NoPutResultSet#getPointOfAttachment
267: */
268: public int getPointOfAttachment() {
269: return pointOfAttachment;
270: }
271:
272: /**
273: * Return the total amount of time spent in this ResultSet
274: *
275: * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
276: * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
277: *
278: * @return long The total amount of time spent (in milliseconds).
279: */
280: public long getTimeSpent(int type) {
281: long totTime = constructorTime + openTime + nextTime
282: + closeTime;
283:
284: if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY) {
285: return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);
286: } else {
287: return totTime;
288: }
289: }
290: }
|