--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.OnceResultSet\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.execute;\r
+\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;\r
+import org.apache.derby.iapi.services.stream.InfoStreams;\r
+\r
+import org.apache.derby.iapi.sql.conn.StatementContext;\r
+\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+/**\r
+ * Takes an expression subquery's result set and verifies that only\r
+ * a single scalar value is being returned.\r
+ * NOTE: A row with a single column containing null will be returned from\r
+ * getNextRow() if the underlying subquery ResultSet is empty.\r
+ *\r
+ */\r
+public class OnceResultSet extends NoPutResultSetImpl\r
+{\r
+ /* Statics for cardinality check */\r
+ public static final int DO_CARDINALITY_CHECK = 1;\r
+ public static final int NO_CARDINALITY_CHECK = 2;\r
+ public static final int UNIQUE_CARDINALITY_CHECK = 3;\r
+\r
+ /* Used to cache row with nulls for case when subquery result set\r
+ * is empty.\r
+ */\r
+ private ExecRow rowWithNulls;\r
+\r
+ /* Used to cache the StatementContext */\r
+ private StatementContext statementContext;\r
+\r
+ // set in constructor and not altered during\r
+ // life of object.\r
+ public NoPutResultSet source;\r
+ private GeneratedMethod emptyRowFun;\r
+ private int cardinalityCheck;\r
+ public int subqueryNumber;\r
+ public int pointOfAttachment;\r
+\r
+ //\r
+ // class interface\r
+ //\r
+ public OnceResultSet(NoPutResultSet s, Activation a, GeneratedMethod emptyRowFun,\r
+ int cardinalityCheck, int resultSetNumber,\r
+ int subqueryNumber, int pointOfAttachment,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost)\r
+ {\r
+ super(a, resultSetNumber, optimizerEstimatedRowCount, optimizerEstimatedCost);\r
+ source = s;\r
+ this.emptyRowFun = emptyRowFun;\r
+ this.cardinalityCheck = cardinalityCheck;\r
+ this.subqueryNumber = subqueryNumber;\r
+ this.pointOfAttachment = pointOfAttachment;\r
+ constructorTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * open a scan on the table. scan parameters are evaluated\r
+ * at each open, so there is probably some way of altering\r
+ * their values...\r
+ *\r
+ * @exception StandardException thrown if cursor finished.\r
+ */\r
+ public void openCore() throws StandardException \r
+ {\r
+ /* NOTE: We can't get code generation\r
+ * to generate calls to reopenCore() for\r
+ * subsequent probes, so we just handle\r
+ * it here.\r
+ */\r
+ if (isOpen)\r
+ {\r
+ reopenCore();\r
+ return;\r
+ }\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ source.openCore();\r
+\r
+ /* Notify StatementContext about ourself so that we can\r
+ * get closed down, if necessary, on an exception.\r
+ */\r
+ if (statementContext == null)\r
+ {\r
+ statementContext = getLanguageConnectionContext().getStatementContext();\r
+ }\r
+ statementContext.setSubqueryResultSet(subqueryNumber, this, \r
+ activation.getNumSubqueries());\r
+\r
+ numOpens++;\r
+ isOpen = true;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * reopen a scan on the table. scan parameters are evaluated\r
+ * at each open, so there is probably some way of altering\r
+ * their values...\r
+ *\r
+ * @exception StandardException thrown if cursor finished.\r
+ */\r
+ public void reopenCore() throws StandardException \r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(isOpen, "OnceResultSet already open");\r
+\r
+ source.reopenCore();\r
+ numOpens++;\r
+\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * Return the requested value computed from the next row. \r
+ *\r
+ * @exception StandardException thrown on failure.\r
+ * StandardException ScalarSubqueryCardinalityViolation\r
+ * Thrown if scalar subquery returns more than 1 row.\r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException \r
+ {\r
+ ExecRow candidateRow = null;\r
+ ExecRow secondRow = null;\r
+ ExecRow result = null;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ // This is an ASSERT and not a real error because this is never\r
+ // outermost in the tree and so a next call when closed will not occur.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT( isOpen, "OpenResultSet not open");\r
+\r
+ if ( isOpen ) \r
+ {\r
+ candidateRow = source.getNextRowCore();\r
+\r
+ if (candidateRow != null)\r
+ {\r
+ switch (cardinalityCheck)\r
+ {\r
+ case DO_CARDINALITY_CHECK:\r
+ case NO_CARDINALITY_CHECK:\r
+ candidateRow = candidateRow.getClone();\r
+ if (cardinalityCheck == DO_CARDINALITY_CHECK)\r
+ {\r
+ /* Raise an error if the subquery returns > 1 row \r
+ * We need to make a copy of the current candidateRow since\r
+ * the getNextRow() for this check will wipe out the underlying\r
+ * row.\r
+ */\r
+ secondRow = source.getNextRowCore();\r
+ if (secondRow != null)\r
+ {\r
+ close();\r
+ StandardException se = StandardException.newException(SQLState.LANG_SCALAR_SUBQUERY_CARDINALITY_VIOLATION);\r
+ throw se;\r
+ }\r
+ }\r
+ result = candidateRow;\r
+ break;\r
+\r
+ case UNIQUE_CARDINALITY_CHECK:\r
+ candidateRow = candidateRow.getClone();\r
+ secondRow = source.getNextRowCore();\r
+ DataValueDescriptor orderable1 = candidateRow.getColumn(1);\r
+ while (secondRow != null)\r
+ {\r
+ DataValueDescriptor orderable2 = secondRow.getColumn(1);\r
+ if (! (orderable1.compare(DataValueDescriptor.ORDER_OP_EQUALS, orderable2, true, true)))\r
+ {\r
+ close();\r
+ StandardException se = StandardException.newException(SQLState.LANG_SCALAR_SUBQUERY_CARDINALITY_VIOLATION);\r
+ throw se;\r
+ }\r
+ secondRow = source.getNextRowCore();\r
+ }\r
+ result = candidateRow;\r
+ break;\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "cardinalityCheck not unexpected to be " +\r
+ cardinalityCheck);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ else if (rowWithNulls == null)\r
+ {\r
+ rowWithNulls = (ExecRow) emptyRowFun.invoke(activation);\r
+ result = rowWithNulls;\r
+ }\r
+ else\r
+ {\r
+ result = rowWithNulls;\r
+ }\r
+ }\r
+\r
+ currentRow = result;\r
+ setCurrentRow(result);\r
+ rowsSeen++;\r
+\r
+ nextTime += getElapsedMillis(beginTime);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * If the result set has been opened,\r
+ * close the open scan.\r
+ *\r
+ * @exception StandardException thrown on error\r
+ */\r
+ public void close() throws StandardException\r
+ {\r
+ beginTime = getCurrentTimeMillis();\r
+ if ( isOpen ) \r
+ {\r
+ // we don't want to keep around a pointer to the\r
+ // row ... so it can be thrown away.\r
+ // REVISIT: does this need to be in a finally\r
+ // block, to ensure that it is executed?\r
+ clearCurrentRow();\r
+\r
+ source.close();\r
+\r
+ super.close();\r
+ }\r
+ else\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("CloseRepeatInfo","Close of OnceResultSet repeated");\r
+\r
+ closeTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#getPointOfAttachment\r
+ */\r
+ public int getPointOfAttachment()\r
+ {\r
+ return pointOfAttachment;\r
+ }\r
+\r
+ /**\r
+ * Return the total amount of time spent in this ResultSet\r
+ *\r
+ * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet\r
+ * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.\r
+ *\r
+ * @return long The total amount of time spent (in milliseconds).\r
+ */\r
+ public long getTimeSpent(int type)\r
+ {\r
+ long totTime = constructorTime + openTime + nextTime + closeTime;\r
+\r
+ if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY)\r
+ {\r
+ return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);\r
+ }\r
+ else\r
+ {\r
+ return totTime;\r
+ }\r
+ }\r
+}\r