--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.HashScanResultSet\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 java.util.List;\r
+import java.util.Properties;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.services.io.FormatableArrayHolder;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.io.FormatableIntHolder;\r
+import org.apache.derby.iapi.services.io.Storable;\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecIndexRow;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+import org.apache.derby.iapi.store.access.BackingStoreHashtable;\r
+import org.apache.derby.iapi.store.access.KeyHasher;\r
+import org.apache.derby.iapi.store.access.Qualifier;\r
+import org.apache.derby.iapi.store.access.RowUtil;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+/**\r
+ * Takes a conglomerate and a table filter builds a hash table on the \r
+ * specified column of the conglomerate on the 1st open. Look up into the\r
+ * hash table is done on the hash key column. The hash table consists of\r
+ * either <code>DataValueDescriptor[]</code>s or <code>List</code>s of\r
+ * <code>DataValueDescriptor[]</code>. The store builds the hash table. When a\r
+ * collision occurs, the store builds a <code>List</code> with the colliding\r
+ * <code>DataValueDescriptor[]</code>s.\r
+ */\r
+public class HashScanResultSet extends ScanResultSet\r
+ implements CursorResultSet\r
+{\r
+ private boolean hashtableBuilt;\r
+ private ExecIndexRow startPosition;\r
+ private ExecIndexRow stopPosition;\r
+ protected ExecRow compactRow;\r
+\r
+ // Variable for managing next() logic on hash entry\r
+ protected boolean firstNext = true;\r
+ private int numFetchedOnNext;\r
+ private int entryVectorSize;\r
+ private List entryVector;\r
+\r
+ // set in constructor and not altered during\r
+ // life of object.\r
+ private long conglomId;\r
+ protected StaticCompiledOpenConglomInfo scoci;\r
+ private GeneratedMethod resultRowAllocator;\r
+ private GeneratedMethod startKeyGetter;\r
+ private int startSearchOperator;\r
+ private GeneratedMethod stopKeyGetter;\r
+ private int stopSearchOperator;\r
+ public Qualifier[][] scanQualifiers;\r
+ public Qualifier[][] nextQualifiers;\r
+ private int initialCapacity;\r
+ private float loadFactor;\r
+ private int maxCapacity;\r
+ public String tableName;\r
+ public String userSuppliedOptimizerOverrides;\r
+ public String indexName;\r
+ public boolean forUpdate;\r
+ private boolean runTimeStatisticsOn;\r
+ private FormatableBitSet accessedCols;\r
+ public int[] keyColumns;\r
+ private boolean sameStartStopPosition;\r
+ private boolean skipNullKeyColumns;\r
+ private boolean keepAfterCommit;\r
+\r
+ protected BackingStoreHashtable hashtable;\r
+ protected boolean eliminateDuplicates; // set to true in DistinctScanResultSet\r
+\r
+ // Run time statistics\r
+ public Properties scanProperties;\r
+ public String startPositionString;\r
+ public String stopPositionString;\r
+ public int hashtableSize;\r
+ public boolean isConstraint;\r
+\r
+ public static final int DEFAULT_INITIAL_CAPACITY = -1;\r
+ public static final float DEFAULT_LOADFACTOR = (float) -1.0;\r
+ public static final int DEFAULT_MAX_CAPACITY = -1;\r
+\r
+\r
+ //\r
+ // class interface\r
+ //\r
+ HashScanResultSet(long conglomId,\r
+ StaticCompiledOpenConglomInfo scoci, Activation activation, \r
+ GeneratedMethod resultRowAllocator, \r
+ int resultSetNumber,\r
+ GeneratedMethod startKeyGetter, int startSearchOperator,\r
+ GeneratedMethod stopKeyGetter, int stopSearchOperator,\r
+ boolean sameStartStopPosition,\r
+ Qualifier[][] scanQualifiers,\r
+ Qualifier[][] nextQualifiers,\r
+ int initialCapacity,\r
+ float loadFactor,\r
+ int maxCapacity,\r
+ int hashKeyItem,\r
+ String tableName,\r
+ String userSuppliedOptimizerOverrides,\r
+ String indexName,\r
+ boolean isConstraint,\r
+ boolean forUpdate,\r
+ int colRefItem,\r
+ int lockMode,\r
+ boolean tableLocked,\r
+ int isolationLevel,\r
+ boolean skipNullKeyColumns,\r
+ double optimizerEstimatedRowCount,\r
+ double optimizerEstimatedCost)\r
+ throws StandardException\r
+ {\r
+ super(activation,\r
+ resultSetNumber,\r
+ resultRowAllocator,\r
+ lockMode, tableLocked, isolationLevel,\r
+ optimizerEstimatedRowCount,\r
+ optimizerEstimatedCost);\r
+ this.scoci = scoci;\r
+ this.conglomId = conglomId;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT( activation!=null, "hash scan must get activation context");\r
+ SanityManager.ASSERT( resultRowAllocator!= null, "hash scan must get row allocator");\r
+ if (sameStartStopPosition)\r
+ {\r
+ SanityManager.ASSERT(stopKeyGetter == null,\r
+ "stopKeyGetter expected to be null when sameStartStopPosition is true");\r
+ }\r
+ }\r
+\r
+ this.resultRowAllocator = resultRowAllocator;\r
+\r
+ this.startKeyGetter = startKeyGetter;\r
+ this.startSearchOperator = startSearchOperator;\r
+ this.stopKeyGetter = stopKeyGetter;\r
+ this.stopSearchOperator = stopSearchOperator;\r
+ this.sameStartStopPosition = sameStartStopPosition;\r
+ this.scanQualifiers = scanQualifiers;\r
+ this.nextQualifiers = nextQualifiers;\r
+ this.initialCapacity = initialCapacity;\r
+ this.loadFactor = loadFactor;\r
+ this.maxCapacity = maxCapacity;\r
+ this.tableName = tableName;\r
+ this.userSuppliedOptimizerOverrides = userSuppliedOptimizerOverrides;\r
+ this.indexName = indexName;\r
+ this.isConstraint = isConstraint;\r
+ this.forUpdate = forUpdate;\r
+ this.skipNullKeyColumns = skipNullKeyColumns;\r
+ this.keepAfterCommit = activation.getResultSetHoldability();\r
+\r
+ /* Retrieve the hash key columns */\r
+ FormatableArrayHolder fah = (FormatableArrayHolder)\r
+ (activation.getPreparedStatement().\r
+ getSavedObject(hashKeyItem));\r
+ FormatableIntHolder[] fihArray = (FormatableIntHolder[]) fah.getArray(FormatableIntHolder.class);\r
+ keyColumns = new int[fihArray.length];\r
+ for (int index = 0; index < fihArray.length; index++)\r
+ {\r
+ keyColumns[index] = fihArray[index].getInt();\r
+ }\r
+\r
+ // retrieve the valid column list from\r
+ // the saved objects, if it exists\r
+ this.accessedCols = null;\r
+ if (colRefItem != -1)\r
+ {\r
+ this.accessedCols = (FormatableBitSet)(activation.getPreparedStatement().\r
+ getSavedObject(colRefItem));\r
+ }\r
+\r
+ runTimeStatisticsOn = \r
+ getLanguageConnectionContext().getRunTimeStatisticsMode();\r
+\r
+ compactRow =\r
+ getCompactRow(candidate, accessedCols, (FormatableBitSet) null, false);\r
+ constructorTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ //\r
+ // ResultSet interface (leftover from NoPutResultSet)\r
+ //\r
+\r
+ /**\r
+ * Can we get instantaneous locks when getting share row\r
+ * locks at READ COMMITTED.\r
+ */\r
+ boolean canGetInstantaneousLocks() {\r
+ return true;\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 on failure to open\r
+ */\r
+ public void openCore() throws StandardException\r
+ {\r
+ TransactionController tc;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT( ! isOpen, "HashScanResultSet already open");\r
+\r
+ // Get the current transaction controller\r
+ tc = activation.getTransactionController();\r
+\r
+ initIsolationLevel();\r
+\r
+ if (startKeyGetter != null)\r
+ {\r
+ startPosition = (ExecIndexRow) startKeyGetter.invoke(activation);\r
+ if (sameStartStopPosition)\r
+ {\r
+ stopPosition = startPosition;\r
+ }\r
+ }\r
+ if (stopKeyGetter != null)\r
+ {\r
+ stopPosition = (ExecIndexRow) stopKeyGetter.invoke(activation);\r
+ }\r
+\r
+ // Check whether there are any comparisons with unordered nulls\r
+ // on either the start or stop position. If there are, we can\r
+ // (and must) skip the scan, because no rows can qualify\r
+ if (skipScan(startPosition, stopPosition))\r
+ {\r
+ // Do nothing\r
+ ;\r
+ }\r
+ else if (! hashtableBuilt)\r
+ {\r
+ DataValueDescriptor[] startPositionRow = \r
+ startPosition == null ? null : startPosition.getRowArray();\r
+ DataValueDescriptor[] stopPositionRow = \r
+ stopPosition == null ? null : stopPosition.getRowArray();\r
+\r
+ hashtable = \r
+ tc.createBackingStoreHashtableFromScan(\r
+ conglomId, // conglomerate to open\r
+ (forUpdate ? TransactionController.OPENMODE_FORUPDATE : 0),\r
+ lockMode,\r
+ isolationLevel,\r
+ accessedCols, \r
+ startPositionRow, \r
+ startSearchOperator,\r
+ scanQualifiers,\r
+ stopPositionRow, \r
+ stopSearchOperator,\r
+ -1, // no limit on total rows.\r
+ keyColumns, \r
+ eliminateDuplicates,// remove duplicates?\r
+ -1, // RESOLVE - is there a row estimate?\r
+ maxCapacity,\r
+ initialCapacity, // in memory Hashtable initial capacity\r
+ loadFactor, // in memory Hashtable load factor\r
+ runTimeStatisticsOn,\r
+ skipNullKeyColumns,\r
+ keepAfterCommit);\r
+\r
+\r
+ if (runTimeStatisticsOn)\r
+ {\r
+ hashtableSize = hashtable.size();\r
+\r
+ if (scanProperties == null)\r
+ {\r
+ scanProperties = new Properties();\r
+ }\r
+\r
+ try\r
+ {\r
+ if (hashtable != null)\r
+ {\r
+ hashtable.getAllRuntimeStats(scanProperties);\r
+ }\r
+ }\r
+ catch(StandardException se)\r
+ {\r
+ // ignore\r
+ }\r
+ }\r
+\r
+\r
+ /* Remember that we created the hash table */\r
+ hashtableBuilt = true;\r
+\r
+ /*\r
+ ** Tell the activation about the number of qualifying rows.\r
+ ** Do this only here, not in reopen, because we don't want\r
+ ** to do this costly operation too often.\r
+ */\r
+ activation.informOfRowCount(this, (long) hashtableSize);\r
+ }\r
+\r
+ isOpen = true;\r
+\r
+ resetProbeVariables();\r
+\r
+ numOpens++;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ /**\r
+ * reopen this ResultSet.\r
+ *\r
+ * @exception StandardException thrown if cursor finished.\r
+ */\r
+ public void reopenCore() throws StandardException {\r
+ TransactionController tc;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(isOpen,\r
+ "HashScanResultSet already open");\r
+ }\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+\r
+ resetProbeVariables();\r
+\r
+ numOpens++;\r
+ openTime += getElapsedMillis(beginTime);\r
+ }\r
+\r
+ private void resetProbeVariables() throws StandardException\r
+ {\r
+ firstNext = true;\r
+ numFetchedOnNext = 0;\r
+ entryVector = null;\r
+ entryVectorSize = 0;\r
+\r
+ if (nextQualifiers != null)\r
+ {\r
+ clearOrderableCache(nextQualifiers);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the next row (if any) from the scan (if open).\r
+ *\r
+ * @exception StandardException thrown on failure to get next row\r
+ */\r
+ public ExecRow getNextRowCore() throws StandardException\r
+ {\r
+ ExecRow result = null;\r
+ DataValueDescriptor[] columns = null;\r
+\r
+ beginTime = getCurrentTimeMillis();\r
+ if ( isOpen && hashtableBuilt)\r
+ {\r
+ /* We use a do/while loop to ensure that we continue down\r
+ * the duplicate chain, if one exists, until we find a\r
+ * row that matches on all probe predicates (or the\r
+ * duplicate chain is exhausted.)\r
+ */\r
+ do \r
+ {\r
+ if (firstNext)\r
+ { \r
+ firstNext = false;\r
+\r
+ /* Hash key could be either a single column or multiple columns.\r
+ * If a single column, then it is the datavalue wrapper, otherwise\r
+ * it is a KeyHasher.\r
+ */\r
+ Object hashEntry;\r
+ if (keyColumns.length == 1)\r
+ {\r
+ hashEntry = hashtable.get(nextQualifiers[0][0].getOrderable());\r
+ }\r
+ else\r
+ {\r
+ KeyHasher mh = new KeyHasher(keyColumns.length);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(nextQualifiers.length == 1);\r
+ }\r
+\r
+ for (int index = 0; index < keyColumns.length; index++)\r
+ {\r
+ // For hashing only use the AND qualifiers \r
+ // located in nextQualifiers[0][0...N], OR \r
+ // qualifiers are checked down a bit by calling\r
+ // qualifyRow on rows returned from hash.\r
+\r
+ DataValueDescriptor dvd = \r
+ nextQualifiers[0][index].getOrderable();\r
+\r
+ if (dvd == null)\r
+ {\r
+ mh = null;\r
+ break;\r
+ }\r
+ mh.setObject(\r
+ index, nextQualifiers[0][index].getOrderable());\r
+ }\r
+ hashEntry = (mh == null) ? null : hashtable.get(mh);\r
+ }\r
+\r
+ if (hashEntry instanceof List)\r
+ {\r
+ entryVector = (List) hashEntry;\r
+ entryVectorSize = entryVector.size();\r
+ columns = \r
+ (DataValueDescriptor[]) entryVector.get(0);\r
+ }\r
+ else\r
+ {\r
+ entryVector = null;\r
+ entryVectorSize = 0;\r
+ columns = (DataValueDescriptor[]) hashEntry;\r
+ }\r
+ }\r
+ else if (numFetchedOnNext < entryVectorSize)\r
+ {\r
+ // We are walking a list and there are more rows left.\r
+ columns = (DataValueDescriptor[]) \r
+ entryVector.get(numFetchedOnNext);\r
+ }\r
+\r
+ if (columns != null)\r
+ {\r
+\r
+ // See if the entry satisfies all of the other qualifiers\r
+\r
+ /* We've already "evaluated" the 1st keyColumns qualifiers \r
+ * when we probed into the hash table, but we need to \r
+ * evaluate them again here because of the behavior of \r
+ * NULLs. NULLs are treated as equal when building and \r
+ * probing the hash table so that we only get a single \r
+ * entry. However, NULL does not equal NULL, so the \r
+ * compare() method below will eliminate any row that\r
+ * has a key column containing a NULL.\r
+ *\r
+ * The following code will also evaluate any OR clauses\r
+ * that may exist, while the above hashing does not \r
+ * include them.\r
+ */\r
+\r
+ if (RowUtil.qualifyRow(columns, nextQualifiers))\r
+ {\r
+ setCompatRow(compactRow, columns);\r
+\r
+ rowsSeen++;\r
+\r
+ result = compactRow;\r
+ }\r
+ else\r
+ {\r
+ result = null;\r
+ }\r
+\r
+ numFetchedOnNext++;\r
+ }\r
+ else\r
+ {\r
+ result = null;\r
+ }\r
+ }\r
+ while (result == null && numFetchedOnNext < entryVectorSize);\r
+\r
+ }\r
+\r
+ currentRow = result;\r
+ setCurrentRow(result);\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
+ if (hashtableBuilt)\r
+ {\r
+ // This is where we get the scan properties for a subquery\r
+ scanProperties = getScanProperties();\r
+ // This is where we get the positioner info for inner tables\r
+ if (runTimeStatisticsOn)\r
+ {\r
+ startPositionString = printStartPosition();\r
+ stopPositionString = printStopPosition();\r
+ }\r
+\r
+ // close the hash table, eating any exception\r
+ hashtable.close();\r
+ hashtable = null;\r
+ hashtableBuilt = false;\r
+ }\r
+ startPosition = null;\r
+ stopPosition = null;\r
+\r
+ super.close();\r
+ }\r
+ else\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("CloseRepeatInfo","Close of HashScanResultSet repeated");\r
+\r
+ closeTime += getElapsedMillis(beginTime);\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
+ /* RESOLVE - subtract out store time later, when available */\r
+ if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY)\r
+ {\r
+ return totTime;\r
+ }\r
+ else\r
+ {\r
+ return totTime;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see NoPutResultSet#requiresRelocking\r
+ */\r
+ public boolean requiresRelocking()\r
+ {\r
+ // IndexRowToBaseRow needs to relock if we didn't keep the lock\r
+ return(\r
+ ((isolationLevel == \r
+ TransactionController.ISOLATION_READ_COMMITTED) ||\r
+ (isolationLevel == \r
+ TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK) ||\r
+ (isolationLevel == \r
+ TransactionController.ISOLATION_READ_UNCOMMITTED)));\r
+\r
+ }\r
+\r
+ //\r
+ // CursorResultSet interface\r
+ //\r
+\r
+ /**\r
+ * This result set has its row location from\r
+ * the last fetch done. If the cursor is closed,\r
+ * a null is returned.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the row location of the current cursor row.\r
+ * @exception StandardException thrown on failure to get row location\r
+ */\r
+ public RowLocation getRowLocation() throws StandardException\r
+ {\r
+ if (! isOpen) return null;\r
+\r
+ if ( ! hashtableBuilt)\r
+ return null;\r
+\r
+ /* This method should only be called if the last column\r
+ * in the current row is a RowLocation.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentRow != null,\r
+ "There must be a current row when fetching the row location");\r
+ Object rlCandidate = currentRow.getColumn(\r
+ currentRow.nColumns());\r
+ if (! (rlCandidate instanceof RowLocation))\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "rlCandidate expected to be instanceof RowLocation, not " +\r
+ rlCandidate.getClass().getName());\r
+ }\r
+ }\r
+\r
+ return (RowLocation) currentRow.getColumn(\r
+ currentRow.nColumns());\r
+ }\r
+\r
+ /**\r
+ * This result set has its row from the last fetch done. \r
+ * If the cursor is closed, a null is returned.\r
+ *\r
+ * @see CursorResultSet\r
+ *\r
+ * @return the last row returned;\r
+ * @exception StandardException thrown on failure.\r
+ */\r
+ /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),\r
+ * once there is such a method. (currentRow is redundant)\r
+ */\r
+ public ExecRow getCurrentRow() throws StandardException \r
+ {\r
+ /* Doesn't make sense to call this method for this node since\r
+ * joins are not updatable.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT( \r
+ "getCurrentRow() not expected to be called for HSRS");\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ public String printStartPosition()\r
+ {\r
+ return printPosition(startSearchOperator, startKeyGetter, startPosition);\r
+ }\r
+\r
+ public String printStopPosition()\r
+ {\r
+ if (sameStartStopPosition)\r
+ {\r
+ return printPosition(stopSearchOperator, startKeyGetter, startPosition);\r
+ }\r
+ else\r
+ {\r
+ return printPosition(stopSearchOperator, stopKeyGetter, stopPosition);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return a start or stop positioner as a String.\r
+ */\r
+ private String printPosition(int searchOperator,\r
+ GeneratedMethod positionGetter,\r
+ ExecIndexRow eiRow)\r
+ {\r
+ String idt = "";\r
+\r
+ String output = "";\r
+ if (positionGetter == null)\r
+ {\r
+ return "\t" +\r
+ MessageService.getTextMessage(SQLState.LANG_NONE) +\r
+ "\n";\r
+ }\r
+\r
+ ExecIndexRow positioner = null;\r
+\r
+ try\r
+ {\r
+ positioner = (ExecIndexRow) positionGetter.invoke(activation);\r
+ }\r
+ catch (StandardException e)\r
+ {\r
+\r
+ if (eiRow == null)\r
+ {\r
+ return "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_POSITION_NOT_AVAIL);\r
+ }\r
+ return "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_UNEXPECTED_EXC_GETTING_POSITIONER) +\r
+ "\n";\r
+ }\r
+\r
+ if (positioner == null)\r
+ {\r
+ return "\t" +\r
+ MessageService.getTextMessage(SQLState.LANG_NONE) +\r
+ "\n";\r
+ }\r
+\r
+ String searchOp = null;\r
+\r
+ switch (searchOperator)\r
+ {\r
+ case ScanController.GE:\r
+ searchOp = ">=";\r
+ break;\r
+\r
+ case ScanController.GT:\r
+ searchOp = ">";\r
+ break;\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT("Unknown search operator " +\r
+ searchOperator);\r
+ }\r
+\r
+ // This is not internationalized because we should never\r
+ // reach here.\r
+ searchOp = "unknown value (" + searchOperator + ")";\r
+ break;\r
+ }\r
+\r
+ output += "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_POSITIONER,\r
+ searchOp,\r
+ String.valueOf(positioner.nColumns()))\r
+ + "\n";\r
+ \r
+ output += "\t" + MessageService.getTextMessage(\r
+ SQLState.LANG_ORDERED_NULL_SEMANTICS) +\r
+ "\n";\r
+ for (int position = 0; position < positioner.nColumns(); position++)\r
+ {\r
+ if (positioner.areNullsOrdered(position))\r
+ {\r
+ output = output + position + " ";\r
+ }\r
+ }\r
+ \r
+ return output + "\n";\r
+ }\r
+\r
+ public Properties getScanProperties()\r
+ {\r
+ return scanProperties;\r
+ }\r
+\r
+ /**\r
+ * Is this ResultSet or it's source result set for update\r
+ * \r
+ * @return Whether or not the result set is for update.\r
+ */\r
+ public boolean isForUpdate()\r
+ {\r
+ return forUpdate;\r
+ }\r
+}\r