001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.HashScanResultSet
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.loader.GeneratedMethod;
025:
026: import org.apache.derby.iapi.services.monitor.Monitor;
027:
028: import org.apache.derby.iapi.services.sanity.SanityManager;
029:
030: import org.apache.derby.iapi.services.io.Storable;
031:
032: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
033: import org.apache.derby.iapi.services.stream.InfoStreams;
034:
035: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
036:
037: import org.apache.derby.iapi.error.StandardException;
038: import org.apache.derby.iapi.services.i18n.MessageService;
039:
040: import org.apache.derby.iapi.reference.SQLState;
041:
042: import org.apache.derby.iapi.sql.execute.CursorResultSet;
043: import org.apache.derby.iapi.sql.execute.ExecIndexRow;
044: import org.apache.derby.iapi.sql.execute.ExecRow;
045: import org.apache.derby.iapi.sql.execute.ExecutionContext;
046: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
047:
048: import org.apache.derby.iapi.sql.Activation;
049: import org.apache.derby.iapi.sql.ResultSet;
050:
051: import org.apache.derby.iapi.store.access.ConglomerateController;
052: import org.apache.derby.iapi.store.access.Qualifier;
053: import org.apache.derby.iapi.store.access.RowUtil;
054: import org.apache.derby.iapi.store.access.ScanController;
055: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
056: import org.apache.derby.iapi.store.access.TransactionController;
057:
058: import org.apache.derby.iapi.types.DataValueDescriptor;
059:
060: import org.apache.derby.iapi.types.Orderable;
061: import org.apache.derby.iapi.types.RowLocation;
062:
063: import org.apache.derby.iapi.store.access.BackingStoreHashtable;
064: import org.apache.derby.iapi.services.io.FormatableBitSet;
065: import org.apache.derby.iapi.services.io.FormatableArrayHolder;
066: import org.apache.derby.iapi.services.io.FormatableIntHolder;
067: import org.apache.derby.iapi.store.access.KeyHasher;
068:
069: import java.util.Enumeration;
070: import java.util.Properties;
071: import java.util.Vector;
072:
073: /**
074: * Takes a conglomerate and a table filter builds a hash table on the
075: * specified column of the conglomerate on the 1st open. Look up into the
076: * hash table is done on the hash key column. The hash table consists of
077: * either DataValueDescriptor[]s or Vectors of DataValueDescriptor[]. The store builds
078: * the hash table. When a collision occurs, the store builds a Vector with
079: * the colliding DataValueDescriptor[]s.
080: *
081: * @author jerry
082: */
083: public class HashScanResultSet extends NoPutResultSetImpl implements
084: CursorResultSet {
085: private boolean hashtableBuilt;
086: private ExecIndexRow startPosition;
087: private ExecIndexRow stopPosition;
088: protected ExecRow candidate; // candidate row is sparse
089: protected ExecRow compactRow;
090:
091: // Variable for managing next() logic on hash entry
092: protected boolean firstNext = true;
093: private int numFetchedOnNext;
094: private int entryVectorSize;
095: private Vector entryVector;
096:
097: // set in constructor and not altered during
098: // life of object.
099: private long conglomId;
100: protected StaticCompiledOpenConglomInfo scoci;
101: private GeneratedMethod resultRowAllocator;
102: private GeneratedMethod startKeyGetter;
103: private int startSearchOperator;
104: private GeneratedMethod stopKeyGetter;
105: private int stopSearchOperator;
106: public Qualifier[][] scanQualifiers;
107: public Qualifier[][] nextQualifiers;
108: private int initialCapacity;
109: private float loadFactor;
110: private int maxCapacity;
111: public String tableName;
112: public String userSuppliedOptimizerOverrides;
113: public String indexName;
114: public boolean forUpdate;
115: private boolean runTimeStatisticsOn;
116: private FormatableBitSet accessedCols;
117: public int isolationLevel;
118: public int lockMode;
119: public int[] keyColumns;
120: private boolean sameStartStopPosition;
121: private boolean skipNullKeyColumns;
122:
123: protected BackingStoreHashtable hashtable;
124: protected boolean eliminateDuplicates; // set to true in DistinctScanResultSet
125:
126: // Run time statistics
127: public Properties scanProperties;
128: public String startPositionString;
129: public String stopPositionString;
130: public int hashtableSize;
131: public boolean isConstraint;
132:
133: public static final int DEFAULT_INITIAL_CAPACITY = -1;
134: public static final float DEFAULT_LOADFACTOR = (float) -1.0;
135: public static final int DEFAULT_MAX_CAPACITY = -1;
136:
137: //
138: // class interface
139: //
140: HashScanResultSet(long conglomId,
141: StaticCompiledOpenConglomInfo scoci, Activation activation,
142: GeneratedMethod resultRowAllocator, int resultSetNumber,
143: GeneratedMethod startKeyGetter, int startSearchOperator,
144: GeneratedMethod stopKeyGetter, int stopSearchOperator,
145: boolean sameStartStopPosition,
146: Qualifier[][] scanQualifiers, Qualifier[][] nextQualifiers,
147: int initialCapacity, float loadFactor, int maxCapacity,
148: int hashKeyItem, String tableName,
149: String userSuppliedOptimizerOverrides, String indexName,
150: boolean isConstraint, boolean forUpdate, int colRefItem,
151: int lockMode, boolean tableLocked, int isolationLevel,
152: boolean skipNullKeyColumns,
153: double optimizerEstimatedRowCount,
154: double optimizerEstimatedCost) throws StandardException {
155: super (activation, resultSetNumber, optimizerEstimatedRowCount,
156: optimizerEstimatedCost);
157: this .scoci = scoci;
158: this .conglomId = conglomId;
159:
160: if (SanityManager.DEBUG) {
161: SanityManager.ASSERT(activation != null,
162: "hash scan must get activation context");
163: SanityManager.ASSERT(resultRowAllocator != null,
164: "hash scan must get row allocator");
165: if (sameStartStopPosition) {
166: SanityManager
167: .ASSERT(stopKeyGetter == null,
168: "stopKeyGetter expected to be null when sameStartStopPosition is true");
169: }
170: }
171:
172: this .resultRowAllocator = resultRowAllocator;
173:
174: this .startKeyGetter = startKeyGetter;
175: this .startSearchOperator = startSearchOperator;
176: this .stopKeyGetter = stopKeyGetter;
177: this .stopSearchOperator = stopSearchOperator;
178: this .sameStartStopPosition = sameStartStopPosition;
179: this .scanQualifiers = scanQualifiers;
180: this .nextQualifiers = nextQualifiers;
181: this .initialCapacity = initialCapacity;
182: this .loadFactor = loadFactor;
183: this .maxCapacity = maxCapacity;
184: this .tableName = tableName;
185: this .userSuppliedOptimizerOverrides = userSuppliedOptimizerOverrides;
186: this .indexName = indexName;
187: this .isConstraint = isConstraint;
188: this .forUpdate = forUpdate;
189: this .skipNullKeyColumns = skipNullKeyColumns;
190:
191: /* Retrieve the hash key columns */
192: FormatableArrayHolder fah = (FormatableArrayHolder) (activation
193: .getPreparedStatement().getSavedObject(hashKeyItem));
194: FormatableIntHolder[] fihArray = (FormatableIntHolder[]) fah
195: .getArray(FormatableIntHolder.class);
196: keyColumns = new int[fihArray.length];
197: for (int index = 0; index < fihArray.length; index++) {
198: keyColumns[index] = fihArray[index].getInt();
199: }
200:
201: // retrieve the valid column list from
202: // the saved objects, if it exists
203: this .accessedCols = null;
204: if (colRefItem != -1) {
205: this .accessedCols = (FormatableBitSet) (activation
206: .getPreparedStatement().getSavedObject(colRefItem));
207: }
208: this .lockMode = lockMode;
209:
210: /* Isolation level - translate from language to store */
211: // If not specified, get current isolation level
212: if (isolationLevel == ExecutionContext.UNSPECIFIED_ISOLATION_LEVEL) {
213: isolationLevel = lcc.getCurrentIsolationLevel();
214: }
215:
216: if (isolationLevel == ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL) {
217: this .isolationLevel = TransactionController.ISOLATION_SERIALIZABLE;
218: } else {
219: /* NOTE: always do row locking on READ COMMITTED/UNCOMMITTED
220: * and repeatable read scans unless the table is marked as
221: * table locked (in sys.systables).
222: *
223: * We always get instantaneous locks as we will complete
224: * the scan before returning any rows and we will fully
225: * requalify the row if we need to go to the heap on a next().
226: */
227:
228: if (!tableLocked) {
229: this .lockMode = TransactionController.MODE_RECORD;
230: }
231:
232: if (isolationLevel == ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL) {
233: this .isolationLevel = TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK;
234: } else if (isolationLevel == ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL) {
235: this .isolationLevel = TransactionController.ISOLATION_READ_UNCOMMITTED;
236: } else if (isolationLevel == ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL) {
237: this .isolationLevel = TransactionController.ISOLATION_REPEATABLE_READ;
238: }
239: }
240:
241: if (SanityManager.DEBUG) {
242: SanityManager
243: .ASSERT(
244: ((isolationLevel == ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL)
245: || (isolationLevel == ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL)
246: || (isolationLevel == ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL) || (isolationLevel == ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL)),
247:
248: "Invalid isolation level - "
249: + isolationLevel);
250: }
251:
252: runTimeStatisticsOn = getLanguageConnectionContext()
253: .getRunTimeStatisticsMode();
254:
255: /* Only call row allocators once */
256: candidate = (ExecRow) resultRowAllocator.invoke(activation);
257: compactRow = getCompactRow(candidate, accessedCols,
258: (FormatableBitSet) null, false);
259: constructorTime += getElapsedMillis(beginTime);
260: }
261:
262: //
263: // ResultSet interface (leftover from NoPutResultSet)
264: //
265:
266: /**
267: * open a scan on the table. scan parameters are evaluated
268: * at each open, so there is probably some way of altering
269: * their values...
270: *
271: * @exception StandardException thrown on failure to open
272: */
273: public void openCore() throws StandardException {
274: TransactionController tc;
275:
276: beginTime = getCurrentTimeMillis();
277: if (SanityManager.DEBUG)
278: SanityManager.ASSERT(!isOpen,
279: "HashScanResultSet already open");
280:
281: // Get the current transaction controller
282: tc = activation.getTransactionController();
283:
284: if (startKeyGetter != null) {
285: startPosition = (ExecIndexRow) startKeyGetter
286: .invoke(activation);
287: if (sameStartStopPosition) {
288: stopPosition = startPosition;
289: }
290: }
291: if (stopKeyGetter != null) {
292: stopPosition = (ExecIndexRow) stopKeyGetter
293: .invoke(activation);
294: }
295:
296: // Check whether there are any comparisons with unordered nulls
297: // on either the start or stop position. If there are, we can
298: // (and must) skip the scan, because no rows can qualify
299: if (skipScan(startPosition, stopPosition)) {
300: // Do nothing
301: ;
302: } else if (!hashtableBuilt) {
303: DataValueDescriptor[] startPositionRow = startPosition == null ? null
304: : startPosition.getRowArray();
305: DataValueDescriptor[] stopPositionRow = stopPosition == null ? null
306: : stopPosition.getRowArray();
307:
308: hashtable = tc
309: .createBackingStoreHashtableFromScan(
310: conglomId, // conglomerate to open
311: (forUpdate ? TransactionController.OPENMODE_FORUPDATE
312: : 0), lockMode, isolationLevel,
313: accessedCols, startPositionRow,
314: startSearchOperator, scanQualifiers,
315: stopPositionRow, stopSearchOperator, -1, // no limit on total rows.
316: keyColumns, eliminateDuplicates,// remove duplicates?
317: -1, // RESOLVE - is there a row estimate?
318: maxCapacity, initialCapacity, // in memory Hashtable initial capacity
319: loadFactor, // in memory Hashtable load factor
320: runTimeStatisticsOn, skipNullKeyColumns);
321:
322: if (runTimeStatisticsOn) {
323: hashtableSize = hashtable.size();
324:
325: if (scanProperties == null) {
326: scanProperties = new Properties();
327: }
328:
329: try {
330: if (hashtable != null) {
331: hashtable.getAllRuntimeStats(scanProperties);
332: }
333: } catch (StandardException se) {
334: // ignore
335: }
336: }
337:
338: /* Remember that we created the hash table */
339: hashtableBuilt = true;
340:
341: /*
342: ** Tell the activation about the number of qualifying rows.
343: ** Do this only here, not in reopen, because we don't want
344: ** to do this costly operation too often.
345: */
346: activation.informOfRowCount(this , (long) hashtableSize);
347: }
348:
349: isOpen = true;
350:
351: resetProbeVariables();
352:
353: numOpens++;
354: openTime += getElapsedMillis(beginTime);
355: }
356:
357: /**
358: * reopen this ResultSet.
359: *
360: * @exception StandardException thrown if cursor finished.
361: */
362: public void reopenCore() throws StandardException {
363: TransactionController tc;
364:
365: if (SanityManager.DEBUG) {
366: SanityManager.ASSERT(isOpen,
367: "HashScanResultSet already open");
368: }
369:
370: beginTime = getCurrentTimeMillis();
371:
372: resetProbeVariables();
373:
374: numOpens++;
375: openTime += getElapsedMillis(beginTime);
376: }
377:
378: private void resetProbeVariables() throws StandardException {
379: firstNext = true;
380: numFetchedOnNext = 0;
381: entryVector = null;
382: entryVectorSize = 0;
383:
384: if (nextQualifiers != null) {
385: clearOrderableCache(nextQualifiers);
386: }
387: }
388:
389: /**
390: * Return the next row (if any) from the scan (if open).
391: *
392: * @exception StandardException thrown on failure to get next row
393: */
394: public ExecRow getNextRowCore() throws StandardException {
395: ExecRow result = null;
396: DataValueDescriptor[] columns = null;
397:
398: beginTime = getCurrentTimeMillis();
399: if (isOpen && hashtableBuilt) {
400: /* We use a do/while loop to ensure that we continue down
401: * the duplicate chain, if one exists, until we find a
402: * row that matches on all probe predicates (or the
403: * duplicate chain is exhausted.)
404: */
405: do {
406: if (firstNext) {
407: firstNext = false;
408:
409: /* Hash key could be either a single column or multiple columns.
410: * If a single column, then it is the datavalue wrapper, otherwise
411: * it is a KeyHasher.
412: */
413: Object hashEntry;
414: if (keyColumns.length == 1) {
415: hashEntry = hashtable.get(nextQualifiers[0][0]
416: .getOrderable());
417: } else {
418: KeyHasher mh = new KeyHasher(keyColumns.length);
419:
420: if (SanityManager.DEBUG) {
421: SanityManager
422: .ASSERT(nextQualifiers.length == 1);
423: }
424:
425: for (int index = 0; index < keyColumns.length; index++) {
426: // For hashing only use the AND qualifiers
427: // located in nextQualifiers[0][0...N], OR
428: // qualifiers are checked down a bit by calling
429: // qualifyRow on rows returned from hash.
430:
431: DataValueDescriptor dvd = nextQualifiers[0][index]
432: .getOrderable();
433:
434: if (dvd == null) {
435: mh = null;
436: break;
437: }
438: mh.setObject(index,
439: nextQualifiers[0][index]
440: .getOrderable());
441: }
442: hashEntry = (mh == null) ? null : hashtable
443: .get(mh);
444: }
445:
446: if (hashEntry instanceof Vector) {
447: entryVector = (Vector) hashEntry;
448: entryVectorSize = entryVector.size();
449: columns = (DataValueDescriptor[]) entryVector
450: .firstElement();
451: } else {
452: entryVector = null;
453: entryVectorSize = 0;
454: columns = (DataValueDescriptor[]) hashEntry;
455: }
456: } else if (numFetchedOnNext < entryVectorSize) {
457: /* We walking a Vector and there's
458: * more rows left in the vector.
459: */
460: columns = (DataValueDescriptor[]) entryVector
461: .elementAt(numFetchedOnNext);
462: }
463:
464: if (columns != null) {
465: if (SanityManager.DEBUG) {
466: // There used to be an assertion here that the columns
467: // array was the same size as the number of columns
468: // in the compact row. This assertion no longer holds
469: // now that we're doing sparse rows, so I deleted it.
470:
471: // Columns is really a Storable[]
472: for (int i = 0; i < columns.length; i++) {
473: if (columns[i] != null
474: && !(columns[i] instanceof Storable)) {
475: SanityManager
476: .THROWASSERT("columns["
477: + i
478: + "] expected to be Storable, not "
479: + columns[i].getClass()
480: .getName());
481: }
482: }
483: }
484:
485: // See if the entry satisfies all of the other qualifiers
486:
487: /* We've already "evaluated" the 1st keyColumns qualifiers
488: * when we probed into the hash table, but we need to
489: * evaluate them again here because of the behavior of
490: * NULLs. NULLs are treated as equal when building and
491: * probing the hash table so that we only get a single
492: * entry. However, NULL does not equal NULL, so the
493: * compare() method below will eliminate any row that
494: * has a key column containing a NULL.
495: *
496: * The following code will also evaluate any OR clauses
497: * that may exist, while the above hashing does not
498: * include them.
499: */
500:
501: if (RowUtil.qualifyRow(columns, nextQualifiers)) {
502: setCompatRow(compactRow, columns);
503:
504: rowsSeen++;
505:
506: result = compactRow;
507: } else {
508: result = null;
509: }
510:
511: numFetchedOnNext++;
512: } else {
513: result = null;
514: }
515: } while (result == null
516: && numFetchedOnNext < entryVectorSize);
517:
518: }
519:
520: currentRow = result;
521: setCurrentRow(result);
522:
523: nextTime += getElapsedMillis(beginTime);
524: return result;
525: }
526:
527: /**
528: * If the result set has been opened,
529: * close the open scan.
530: *
531: * @exception StandardException thrown on error
532: */
533: public void close() throws StandardException {
534: beginTime = getCurrentTimeMillis();
535: if (isOpen) {
536: // we don't want to keep around a pointer to the
537: // row ... so it can be thrown away.
538: // REVISIT: does this need to be in a finally
539: // block, to ensure that it is executed?
540: clearCurrentRow();
541:
542: if (hashtableBuilt) {
543: // This is where we get the scan properties for a subquery
544: scanProperties = getScanProperties();
545: // This is where we get the positioner info for inner tables
546: if (runTimeStatisticsOn) {
547: startPositionString = printStartPosition();
548: stopPositionString = printStopPosition();
549: }
550:
551: // close the hash table, eating any exception
552: hashtable.close();
553: hashtable = null;
554: hashtableBuilt = false;
555: }
556: startPosition = null;
557: stopPosition = null;
558:
559: super .close();
560: } else if (SanityManager.DEBUG)
561: SanityManager.DEBUG("CloseRepeatInfo",
562: "Close of HashScanResultSet repeated");
563:
564: closeTime += getElapsedMillis(beginTime);
565: }
566:
567: /**
568: * Return the total amount of time spent in this ResultSet
569: *
570: * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
571: * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
572: *
573: * @return long The total amount of time spent (in milliseconds).
574: */
575: public long getTimeSpent(int type) {
576: long totTime = constructorTime + openTime + nextTime
577: + closeTime;
578:
579: /* RESOLVE - subtract out store time later, when available */
580: if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY) {
581: return totTime;
582: } else {
583: return totTime;
584: }
585: }
586:
587: /**
588: * @see NoPutResultSet#getScanIsolationLevel
589: */
590: public int getScanIsolationLevel() {
591: return isolationLevel;
592: }
593:
594: /**
595: * @see NoPutResultSet#requiresRelocking
596: */
597: public boolean requiresRelocking() {
598: // IndexRowToBaseRow needs to relock if we didn't keep the lock
599: return (((isolationLevel == TransactionController.ISOLATION_READ_COMMITTED)
600: || (isolationLevel == TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK) || (isolationLevel == TransactionController.ISOLATION_READ_UNCOMMITTED)));
601:
602: }
603:
604: //
605: // CursorResultSet interface
606: //
607:
608: /**
609: * This result set has its row location from
610: * the last fetch done. If the cursor is closed,
611: * a null is returned.
612: *
613: * @see CursorResultSet
614: *
615: * @return the row location of the current cursor row.
616: * @exception StandardException thrown on failure to get row location
617: */
618: public RowLocation getRowLocation() throws StandardException {
619: if (!isOpen)
620: return null;
621:
622: if (!hashtableBuilt)
623: return null;
624:
625: /* This method should only be called if the last column
626: * in the current row is a RowLocation.
627: */
628: if (SanityManager.DEBUG) {
629: SanityManager
630: .ASSERT(currentRow != null,
631: "There must be a current row when fetching the row location");
632: Object rlCandidate = currentRow.getColumn(currentRow
633: .nColumns());
634: if (!(rlCandidate instanceof RowLocation)) {
635: SanityManager
636: .THROWASSERT("rlCandidate expected to be instanceof RowLocation, not "
637: + rlCandidate.getClass().getName());
638: }
639: }
640:
641: return (RowLocation) currentRow
642: .getColumn(currentRow.nColumns());
643: }
644:
645: /**
646: * This result set has its row from the last fetch done.
647: * If the cursor is closed, a null is returned.
648: *
649: * @see CursorResultSet
650: *
651: * @return the last row returned;
652: * @exception StandardException thrown on failure.
653: */
654: /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
655: * once there is such a method. (currentRow is redundant)
656: */
657: public ExecRow getCurrentRow() throws StandardException {
658: /* Doesn't make sense to call this method for this node since
659: * joins are not updatable.
660: */
661: if (SanityManager.DEBUG) {
662: SanityManager
663: .THROWASSERT("getCurrentRow() not expected to be called for HSRS");
664: }
665:
666: return null;
667: }
668:
669: public String printStartPosition() {
670: return printPosition(startSearchOperator, startKeyGetter,
671: startPosition);
672: }
673:
674: public String printStopPosition() {
675: if (sameStartStopPosition) {
676: return printPosition(stopSearchOperator, startKeyGetter,
677: startPosition);
678: } else {
679: return printPosition(stopSearchOperator, stopKeyGetter,
680: stopPosition);
681: }
682: }
683:
684: /**
685: * Return a start or stop positioner as a String.
686: */
687: private String printPosition(int searchOperator,
688: GeneratedMethod positionGetter, ExecIndexRow eiRow) {
689: String idt = "";
690:
691: String output = "";
692: if (positionGetter == null) {
693: return "\t"
694: + MessageService.getTextMessage(SQLState.LANG_NONE)
695: + "\n";
696: }
697:
698: ExecIndexRow positioner = null;
699:
700: try {
701: positioner = (ExecIndexRow) positionGetter
702: .invoke(activation);
703: } catch (StandardException e) {
704:
705: if (eiRow == null) {
706: return "\t"
707: + MessageService
708: .getTextMessage(SQLState.LANG_POSITION_NOT_AVAIL);
709: }
710: return "\t"
711: + MessageService
712: .getTextMessage(SQLState.LANG_UNEXPECTED_EXC_GETTING_POSITIONER)
713: + "\n";
714: }
715:
716: if (positioner == null) {
717: return "\t"
718: + MessageService.getTextMessage(SQLState.LANG_NONE)
719: + "\n";
720: }
721:
722: String searchOp = null;
723:
724: switch (searchOperator) {
725: case ScanController.GE:
726: searchOp = ">=";
727: break;
728:
729: case ScanController.GT:
730: searchOp = ">";
731: break;
732:
733: default:
734: if (SanityManager.DEBUG) {
735: SanityManager.THROWASSERT("Unknown search operator "
736: + searchOperator);
737: }
738:
739: // This is not internationalized because we should never
740: // reach here.
741: searchOp = "unknown value (" + searchOperator + ")";
742: break;
743: }
744:
745: output += "\t"
746: + MessageService.getTextMessage(
747: SQLState.LANG_POSITIONER, searchOp, String
748: .valueOf(positioner.nColumns())) + "\n";
749:
750: output += "\t"
751: + MessageService
752: .getTextMessage(SQLState.LANG_ORDERED_NULL_SEMANTICS)
753: + "\n";
754: for (int position = 0; position < positioner.nColumns(); position++) {
755: if (positioner.areNullsOrdered(position)) {
756: output = output + position + " ";
757: }
758: }
759:
760: return output + "\n";
761: }
762:
763: public Properties getScanProperties() {
764: return scanProperties;
765: }
766:
767: /**
768: * Is this ResultSet or it's source result set for update
769: *
770: * @return Whether or not the result set is for update.
771: */
772: public boolean isForUpdate() {
773: return forUpdate;
774: }
775: }
|