0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.sql.execute.TableScanResultSet
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.impl.sql.execute;
0023:
0024: import org.apache.derby.iapi.sql.execute.CursorResultSet;
0025: import org.apache.derby.iapi.error.StandardException;
0026: import org.apache.derby.iapi.services.i18n.MessageService;
0027:
0028: import org.apache.derby.iapi.sql.Activation;
0029: import org.apache.derby.iapi.sql.ResultSet;
0030: import org.apache.derby.iapi.sql.execute.ExecRow;
0031: import org.apache.derby.iapi.sql.execute.ExecIndexRow;
0032: import org.apache.derby.iapi.sql.execute.ExecutionContext;
0033: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
0034: import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;
0035:
0036: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
0037:
0038: import org.apache.derby.iapi.store.access.ConglomerateController;
0039: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
0040: import org.apache.derby.iapi.store.access.Qualifier;
0041: import org.apache.derby.iapi.store.access.ScanController;
0042: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
0043: import org.apache.derby.iapi.store.access.TransactionController;
0044:
0045: import org.apache.derby.iapi.types.DataValueDescriptor;
0046:
0047: import org.apache.derby.iapi.types.Orderable;
0048: import org.apache.derby.iapi.types.RowLocation;
0049:
0050: import org.apache.derby.iapi.services.sanity.SanityManager;
0051:
0052: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
0053: import org.apache.derby.iapi.services.stream.InfoStreams;
0054:
0055: import org.apache.derby.iapi.services.monitor.Monitor;
0056:
0057: import org.apache.derby.iapi.services.loader.GeneratedMethod;
0058: import org.apache.derby.iapi.services.io.FormatableBitSet;
0059:
0060: import org.apache.derby.iapi.reference.SQLState;
0061:
0062: import java.util.Properties;
0063: import java.util.Hashtable;
0064:
0065: /**
0066: * Takes a table and a table filter and returns
0067: * the table's rows satisfying the filter as a result set.
0068: *
0069: * There are several things we could do during object
0070: * construction that are done in the open & next calls, to
0071: * improve performance.
0072: *
0073: * @author ames
0074: */
0075: class TableScanResultSet extends NoPutResultSetImpl implements
0076: CursorResultSet, Cloneable {
0077: protected ScanController scanController;
0078: protected boolean scanControllerOpened;
0079: protected boolean isKeyed;
0080: protected boolean firstScan = true;
0081: protected ExecIndexRow startPosition;
0082: protected ExecIndexRow stopPosition;
0083: protected ExecRow candidate;
0084:
0085: // set in constructor and not altered during
0086: // life of object.
0087: protected long conglomId;
0088: protected DynamicCompiledOpenConglomInfo dcoci;
0089: protected StaticCompiledOpenConglomInfo scoci;
0090: protected GeneratedMethod resultRowAllocator;
0091: protected GeneratedMethod startKeyGetter;
0092: protected int startSearchOperator;
0093: protected GeneratedMethod stopKeyGetter;
0094: protected int stopSearchOperator;
0095: public Qualifier[][] qualifiers;
0096: public String tableName;
0097: public String userSuppliedOptimizerOverrides;
0098: public String indexName;
0099: protected boolean runTimeStatisticsOn;
0100: protected FormatableBitSet accessedCols;
0101: protected int[] indexCols; //index keys base column position array
0102: public int rowsPerRead;
0103: public boolean forUpdate;
0104: private boolean sameStartStopPosition;
0105: private boolean nextDone;
0106: private RowLocation rlTemplate;
0107:
0108: public int isolationLevel;
0109: public int lockMode;
0110:
0111: // Run time statistics
0112: private Properties scanProperties;
0113: public String startPositionString;
0114: public String stopPositionString;
0115: public boolean isConstraint;
0116: public boolean coarserLock;
0117: public boolean oneRowScan;
0118:
0119: protected long rowsThisScan;
0120:
0121: private long estimatedRowCount;
0122:
0123: /* Following fields are used by beetle 3865, updateable cursor using index. "past2FutureTbl"
0124: * is a hash table containing updated rows that are thrown into future direction of the
0125: * index scan and as a result we'll hit it again but should skip it. If this hash table
0126: * is full, we scan forward and have a virtual memory style temp heap holding future row
0127: * id's.
0128: */
0129: protected Hashtable past2FutureTbl;
0130: protected TemporaryRowHolder futureForUpdateRows; //tmp table for materialized rids
0131: protected TemporaryRowHolderResultSet futureRowResultSet; //result set for reading from above
0132: protected boolean skipFutureRowHolder; //skip reading rows from above
0133: protected boolean sourceDrained; //all row ids materialized
0134: protected boolean currentRowPrescanned; //got a row from above tmp table
0135: protected boolean compareToLastKey; //see comments in UpdateResultSet
0136: protected ExecRow lastCursorKey;
0137: private ExecRow sparseRow; //sparse row in heap column order
0138: private FormatableBitSet sparseRowMap; //which columns to read
0139:
0140: // For Scrollable insensitive updatable result sets, only qualify a row the
0141: // first time it's been read, since an update can change a row so that it
0142: // no longer qualifies
0143: private boolean qualify;
0144:
0145: // currentRowIsValid is set to the result of positioning at a rowLocation.
0146: // It will be true if the positioning was successful and false if the row
0147: // was deleted under our feet. Whenenver currentRowIsValid is false it means
0148: // that the row has been deleted.
0149: private boolean currentRowIsValid;
0150:
0151: // Indicates whether the scan has been positioned back to a previously read
0152: // row, or it is accessing a row for the first time.
0153: private boolean scanRepositioned;
0154:
0155: //
0156: // class interface
0157: //
0158: TableScanResultSet(long conglomId,
0159: StaticCompiledOpenConglomInfo scoci, Activation activation,
0160: GeneratedMethod resultRowAllocator, int resultSetNumber,
0161: GeneratedMethod startKeyGetter, int startSearchOperator,
0162: GeneratedMethod stopKeyGetter, int stopSearchOperator,
0163: boolean sameStartStopPosition, Qualifier[][] qualifiers,
0164: String tableName, String userSuppliedOptimizerOverrides,
0165: String indexName, boolean isConstraint, boolean forUpdate,
0166: int colRefItem, int indexColItem, int lockMode,
0167: boolean tableLocked, int isolationLevel, int rowsPerRead,
0168: boolean oneRowScan, double optimizerEstimatedRowCount,
0169: double optimizerEstimatedCost) throws StandardException {
0170: super (activation, resultSetNumber, optimizerEstimatedRowCount,
0171: optimizerEstimatedCost);
0172:
0173: this .conglomId = conglomId;
0174:
0175: /* Static info created at compile time and can be shared across
0176: * instances of the plan.
0177: * Dynamic info created on 1st opening of this ResultSet as
0178: * it cannot be shared.
0179: */
0180: this .scoci = scoci;
0181:
0182: if (SanityManager.DEBUG) {
0183: SanityManager.ASSERT(activation != null,
0184: "table scan must get activation context");
0185: SanityManager.ASSERT(resultRowAllocator != null,
0186: "table scan must get row allocator");
0187: if (sameStartStopPosition) {
0188: SanityManager
0189: .ASSERT(stopKeyGetter == null,
0190: "stopKeyGetter expected to be null when sameStartStopPosition is true");
0191: }
0192: }
0193:
0194: this .resultRowAllocator = resultRowAllocator;
0195:
0196: this .startKeyGetter = startKeyGetter;
0197: this .startSearchOperator = startSearchOperator;
0198: this .stopKeyGetter = stopKeyGetter;
0199: this .stopSearchOperator = stopSearchOperator;
0200: this .sameStartStopPosition = sameStartStopPosition;
0201: this .qualifiers = qualifiers;
0202: this .tableName = tableName;
0203: this .userSuppliedOptimizerOverrides = userSuppliedOptimizerOverrides;
0204: this .indexName = indexName;
0205: this .isConstraint = isConstraint;
0206: this .forUpdate = forUpdate;
0207: this .rowsPerRead = rowsPerRead;
0208: this .oneRowScan = oneRowScan;
0209:
0210: // retrieve the valid column list from
0211: // the saved objects, if it exists
0212: this .accessedCols = null;
0213: if (colRefItem != -1) {
0214: this .accessedCols = (FormatableBitSet) (activation
0215: .getPreparedStatement().getSavedObject(colRefItem));
0216: }
0217: if (indexColItem != -1) {
0218: this .indexCols = (int[]) (activation.getPreparedStatement()
0219: .getSavedObject(indexColItem));
0220: }
0221: if (indexCols != null)
0222: activation.setForUpdateIndexScan(this );
0223:
0224: this .lockMode = lockMode;
0225:
0226: /* Isolation level - translate from language to store */
0227: // If not specified, get current isolation level
0228: if (isolationLevel == ExecutionContext.UNSPECIFIED_ISOLATION_LEVEL) {
0229: isolationLevel = lcc.getCurrentIsolationLevel();
0230: }
0231:
0232: if (isolationLevel == ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL) {
0233: this .isolationLevel = TransactionController.ISOLATION_SERIALIZABLE;
0234: } else {
0235: /* NOTE: always do row locking on READ COMMITTED/UNCOMITTED scans,
0236: * unless the table is marked as table locked (in sys.systables)
0237: * This is to improve concurrency. Also see FromBaseTable's
0238: * updateTargetLockMode (KEEP THESE TWO PLACES CONSISTENT!
0239: * bug 4318).
0240: */
0241:
0242: /* NOTE: always do row locking on READ COMMITTED/UNCOMMITTED
0243: * and repeatable read scans unless the table is marked as
0244: * table locked (in sys.systables).
0245: *
0246: * We always get instantaneous locks as we will complete
0247: * the scan before returning any rows and we will fully
0248: * requalify the row if we need to go to the heap on a next().
0249: */
0250:
0251: if (!tableLocked) {
0252: this .lockMode = TransactionController.MODE_RECORD;
0253: }
0254:
0255: if (isolationLevel == ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL) {
0256: /*
0257: * Now we see if we can get instantaneous locks
0258: * if we are getting share locks.
0259: * (For example, we can get instantaneous locks
0260: * when doing a bulk fetch.)
0261: */
0262: if ((!forUpdate) && canGetInstantaneousLocks()) {
0263: this .isolationLevel = TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK;
0264: } else {
0265: this .isolationLevel = TransactionController.ISOLATION_READ_COMMITTED;
0266: }
0267: } else if (isolationLevel == ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL) {
0268: this .isolationLevel = TransactionController.ISOLATION_READ_UNCOMMITTED;
0269: } else if (isolationLevel == ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL) {
0270: this .isolationLevel = TransactionController.ISOLATION_REPEATABLE_READ;
0271: }
0272: }
0273:
0274: if (SanityManager.DEBUG) {
0275: SanityManager
0276: .ASSERT(
0277: ((isolationLevel == ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL)
0278: || (isolationLevel == ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL)
0279: || (isolationLevel == ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL) || (isolationLevel == ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL)),
0280:
0281: "Invalid isolation level - "
0282: + isolationLevel);
0283: }
0284:
0285: runTimeStatisticsOn = (activation != null && activation
0286: .getLanguageConnectionContext()
0287: .getRunTimeStatisticsMode());
0288:
0289: /* Only call row allocators once */
0290: candidate = (ExecRow) resultRowAllocator.invoke(activation);
0291: constructorTime += getElapsedMillis(beginTime);
0292:
0293: /* Always qualify the first time a row is being read */
0294: qualify = true;
0295: currentRowIsValid = false;
0296: scanRepositioned = false;
0297: }
0298:
0299: //
0300: // ResultSet interface (leftover from NoPutResultSet)
0301: //
0302:
0303: /**
0304: * open a scan on the table. scan parameters are evaluated
0305: * at each open, so there is probably some way of altering
0306: * their values...
0307: *
0308: * @exception StandardException thrown on failure to open
0309: */
0310: public void openCore() throws StandardException {
0311: if (SanityManager.DEBUG)
0312: SanityManager.ASSERT(!isOpen,
0313: "TableScanResultSet already open");
0314:
0315: // Get the current transaction controller
0316: TransactionController tc = activation
0317: .getTransactionController();
0318:
0319: if (dcoci == null)
0320: dcoci = tc.getDynamicCompiledConglomInfo(conglomId);
0321:
0322: if (startKeyGetter != null) {
0323: startPosition = (ExecIndexRow) startKeyGetter
0324: .invoke(activation);
0325: if (sameStartStopPosition) {
0326: stopPosition = startPosition;
0327: }
0328: }
0329: if (stopKeyGetter != null) {
0330: stopPosition = (ExecIndexRow) stopKeyGetter
0331: .invoke(activation);
0332: }
0333:
0334: /* NOTE: We always open the ScanController on the 1st open
0335: * to do the keyed conglomerate check.
0336: */
0337:
0338: // Determine whether the conglomerate is keyed. This determines
0339: // how we find the RowLocation for the base heap. For non-keyed
0340: // conglomerates, we ask the scan. For keyed conglomerates, it
0341: // is the last column in the row.
0342: //
0343: // Do this here, rather than in the constructor, so we can avoid
0344: // throwing exceptions from the constructor
0345: if (firstScan) {
0346: openScanController(tc);
0347:
0348: isKeyed = scanController.isKeyed();
0349:
0350: /*
0351: ** If scan tracing is turned on, print information about this
0352: ** TableScanResultSet when it is first opened. We would like
0353: ** to do this when it is constructed, but it is not always
0354: ** possible to get the start and stop positioners at the time
0355: ** this object is constructed (because they may depend on outer
0356: ** rows).
0357: */
0358: if (SanityManager.DEBUG) {
0359: if (SanityManager.DEBUG_ON("ScanTrace")) {
0360: //traceScanParameters();
0361: }
0362: }
0363: }
0364:
0365: // Check whether there are any comparisons with unordered nulls
0366: // on either the start or stop position. If there are, we can
0367: // (and must) skip the scan, because no rows can qualify
0368: if (skipScan(startPosition, stopPosition)) {
0369: scanControllerOpened = false;
0370: }
0371: /* NOTE: We always open the ScanController on the 1st open
0372: * to do the keyed conglomerate check, so we only need to
0373: * do it here if not the 1st scan.
0374: */
0375: else if (!firstScan) {
0376: openScanController(tc);
0377: }
0378:
0379: /* If the scan is on an index and opened for update,
0380: * then we cache the scan controller and conglomerate
0381: * number in the activation so that the scan controller
0382: * can be re-used by the update/delete if the index
0383: * that we are scanning also needs to be updated.
0384: */
0385: if (forUpdate && isKeyed) {
0386: activation.setIndexScanController(scanController);
0387: activation.setIndexConglomerateNumber(conglomId);
0388: }
0389:
0390: firstScan = false;
0391: isOpen = true;
0392: numOpens++;
0393: nextDone = false;
0394: openTime += getElapsedMillis(beginTime);
0395: }
0396:
0397: /*
0398: ** Open the scan controller
0399: **
0400: ** @param transaction controller will open one if null
0401: */
0402: protected void openScanController(TransactionController tc)
0403: throws StandardException {
0404: DataValueDescriptor[] startPositionRow = startPosition == null ? null
0405: : startPosition.getRowArray();
0406: DataValueDescriptor[] stopPositionRow = stopPosition == null ? null
0407: : stopPosition.getRowArray();
0408:
0409: // Clear the Qualifiers's Orderable cache
0410: if (qualifiers != null) {
0411: clearOrderableCache(qualifiers);
0412: }
0413:
0414: // Get the current transaction controller
0415: if (tc == null)
0416: tc = activation.getTransactionController();
0417:
0418: int openMode = 0;
0419: if (forUpdate) {
0420: openMode = TransactionController.OPENMODE_FORUPDATE;
0421:
0422: if (activation.isCursorActivation())
0423: openMode |= TransactionController.OPENMODE_USE_UPDATE_LOCKS;
0424: }
0425:
0426: scanController = tc.openCompiledScan(activation
0427: .getResultSetHoldability(), openMode, lockMode,
0428: isolationLevel, accessedCols, startPositionRow,
0429: // not used when giving null start position
0430: startSearchOperator, qualifiers, stopPositionRow,
0431: // not used when giving null stop position
0432: stopSearchOperator, scoci, dcoci);
0433:
0434: /* Remember that we opened the scan */
0435: scanControllerOpened = true;
0436:
0437: rowsThisScan = 0;
0438:
0439: /*
0440: ** Inform the activation of the estimated number of rows. Only
0441: ** do it here, not in reopen, so that we don't do this costly
0442: ** check too often.
0443: */
0444: estimatedRowCount = scanController.getEstimatedRowCount();
0445: activation.informOfRowCount(this , scanController
0446: .getEstimatedRowCount());
0447: }
0448:
0449: /*
0450: ** reopen the scan controller
0451: */
0452: private void reopenScanController() throws StandardException {
0453: DataValueDescriptor[] startPositionRow = startPosition == null ? null
0454: : startPosition.getRowArray();
0455: DataValueDescriptor[] stopPositionRow = stopPosition == null ? null
0456: : stopPosition.getRowArray();
0457:
0458: // Clear the Qualifiers's Orderable cache
0459: if (qualifiers != null) {
0460: clearOrderableCache(qualifiers);
0461: }
0462:
0463: scanController.reopenScan(startPositionRow,
0464: startSearchOperator, qualifiers, stopPositionRow,
0465: stopSearchOperator);
0466:
0467: /* Remember that we opened the scan */
0468: scanControllerOpened = true;
0469:
0470: rowsThisScan = 0;
0471: }
0472:
0473: /**
0474: * Reopen a table scan. Here we take advantage
0475: * of the reopenScan() interface on scanController
0476: * for optimimal performance on joins where we are
0477: * an inner table.
0478: *
0479: * @exception StandardException thrown on failure to open
0480: */
0481: public void reopenCore() throws StandardException {
0482: beginTime = getCurrentTimeMillis();
0483: if (SanityManager.DEBUG)
0484: SanityManager.ASSERT(isOpen,
0485: "TableScanResultSet not open, cannot reopen");
0486:
0487: if (startKeyGetter != null) {
0488: startPosition = (ExecIndexRow) startKeyGetter
0489: .invoke(activation);
0490: if (sameStartStopPosition) {
0491: stopPosition = startPosition;
0492: }
0493: }
0494: if (stopKeyGetter != null) {
0495: stopPosition = (ExecIndexRow) stopKeyGetter
0496: .invoke(activation);
0497: }
0498:
0499: // Check whether there are any comparisons with unordered nulls
0500: // on either the start or stop position. If there are, we can
0501: // (and must) skip the scan, because no rows can qualify
0502: if (skipScan(startPosition, stopPosition)) {
0503: scanControllerOpened = false;
0504: } else {
0505: if (scanController == null)
0506: openScanController((TransactionController) null);
0507: else
0508: reopenScanController();
0509:
0510: }
0511:
0512: numOpens++;
0513: nextDone = false;
0514: openTime += getElapsedMillis(beginTime);
0515: }
0516:
0517: /**
0518: * Check and make sure sparse heap row and accessed bit map are created.
0519: * beetle 3865, update cursor using index.
0520: *
0521: * @exception StandardException thrown on failure
0522: */
0523: private void getSparseRowAndMap() throws StandardException {
0524: int numCols = 1, colPos;
0525: for (int i = 0; i < indexCols.length; i++) {
0526: colPos = (indexCols[i] > 0) ? indexCols[i] : -indexCols[i];
0527: if (colPos > numCols)
0528: numCols = colPos;
0529: }
0530: sparseRow = new ValueRow(numCols);
0531: sparseRowMap = new FormatableBitSet(numCols);
0532: for (int i = 0; i < indexCols.length; i++) {
0533: if (accessedCols.get(i)) {
0534: colPos = (indexCols[i] > 0) ? indexCols[i]
0535: : -indexCols[i];
0536: sparseRow.setColumn(colPos, candidate.getColumn(i + 1));
0537: sparseRowMap.set(colPos - 1);
0538: }
0539: }
0540: }
0541:
0542: /**
0543: * Return the next row (if any) from the scan (if open).
0544: *
0545: * @exception StandardException thrown on failure to get next row
0546: */
0547: public ExecRow getNextRowCore() throws StandardException {
0548: checkCancellationFlag();
0549:
0550: if (currentRow == null || scanRepositioned) {
0551: currentRow = getCompactRow(candidate, accessedCols,
0552: (FormatableBitSet) null, isKeyed);
0553: }
0554:
0555: beginTime = getCurrentTimeMillis();
0556:
0557: ExecRow result = null;
0558:
0559: /* beetle 3865, updateable cursor using index. We first saved updated rows with new value
0560: * falling into future direction of index scan in hash table, if it's full, we scanned
0561: * forward and saved future row ids in a virtual mem heap.
0562: */
0563: if (futureForUpdateRows != null) {
0564: currentRowPrescanned = false;
0565: if (!skipFutureRowHolder) {
0566: if (futureRowResultSet == null) {
0567: futureRowResultSet = (TemporaryRowHolderResultSet) futureForUpdateRows
0568: .getResultSet();
0569: futureRowResultSet.openCore();
0570: }
0571:
0572: ExecRow ridRow = futureRowResultSet.getNextRowCore();
0573:
0574: if (ridRow != null) {
0575: /* to boost performance, we used virtual mem heap, and we can insert after
0576: * we start retrieving results. The assumption is to
0577: * delete current row right after we retrieve it.
0578: */
0579: futureRowResultSet.deleteCurrentRow();
0580: RowLocation rl = (RowLocation) ridRow.getColumn(1);
0581: ConglomerateController baseCC = activation
0582: .getHeapConglomerateController();
0583: if (sparseRow == null)
0584: getSparseRowAndMap();
0585: baseCC.fetch(rl, sparseRow.getRowArray(),
0586: sparseRowMap);
0587: RowLocation rl2 = (RowLocation) rl.getClone();
0588: currentRow.setColumn(currentRow.nColumns(), rl2);
0589: candidate.setColumn(candidate.nColumns(), rl2); // have to be consistent!
0590:
0591: result = currentRow;
0592: currentRowPrescanned = true;
0593: } else if (sourceDrained) {
0594: currentRowPrescanned = true;
0595: currentRow = null;
0596: }
0597:
0598: if (currentRowPrescanned) {
0599: setCurrentRow(result);
0600:
0601: nextTime += getElapsedMillis(beginTime);
0602: return result;
0603: }
0604: }
0605: }
0606:
0607: if (isOpen && !nextDone) {
0608: /* Only need to do 1 next per scan
0609: * for 1 row scans.
0610: */
0611: nextDone = oneRowScan;
0612:
0613: if (scanControllerOpened) {
0614: boolean moreRows;
0615:
0616: while (moreRows = scanController.fetchNext(candidate
0617: .getRowArray())) {
0618: rowsSeen++;
0619: rowsThisScan++;
0620:
0621: /*
0622: ** Skip rows where there are start or stop positioners
0623: ** that do not implement ordered null semantics and
0624: ** there are columns in those positions that contain
0625: ** null.
0626: ** No need to check if start and stop positions are the
0627: ** same, since all predicates in both will be ='s,
0628: ** and hence evaluated in the store.
0629: */
0630: if ((!sameStartStopPosition) && skipRow(candidate)) {
0631: rowsFiltered++;
0632: continue;
0633: }
0634:
0635: /* beetle 3865, updateable cursor use index. If we have a hash table that
0636: * holds updated records, and we hit it again, skip it, and remove it from
0637: * hash since we can't hit it again, and we have a space in hash, so can
0638: * stop scanning forward.
0639: */
0640: if (past2FutureTbl != null) {
0641: RowLocation rowLoc = (RowLocation) currentRow
0642: .getColumn(currentRow.nColumns());
0643: if (past2FutureTbl.get(rowLoc) != null) {
0644: past2FutureTbl.remove(rowLoc);
0645: continue;
0646: }
0647: }
0648:
0649: result = currentRow;
0650:
0651: break;
0652: }
0653:
0654: /*
0655: ** If we just finished a full scan of the heap, update
0656: ** the number of rows in the scan controller.
0657: **
0658: ** NOTE: It would be more efficient to only update the
0659: ** scan controller if the optimizer's estimated number of
0660: ** rows were wrong by more than some threshold (like 10%).
0661: ** This would require a little more work than I have the
0662: ** time for now, however, as the row estimate that is given
0663: ** to this result set is the total number of rows for all
0664: ** scans, not the number of rows per scan.
0665: */
0666: if (!moreRows) {
0667: setRowCountIfPossible(rowsThisScan);
0668: currentRow = null;
0669: }
0670: }
0671: }
0672:
0673: setCurrentRow(result);
0674: currentRowIsValid = true;
0675: scanRepositioned = false;
0676: qualify = true;
0677:
0678: nextTime += getElapsedMillis(beginTime);
0679: return result;
0680: }
0681:
0682: /**
0683: * If the result set has been opened,
0684: * close the open scan.
0685: * @exception StandardException on error
0686: */
0687: public void close() throws StandardException {
0688: beginTime = getCurrentTimeMillis();
0689: if (isOpen) {
0690: /*
0691: ** If scan tracing is turned on, print information about this
0692: ** TableScanResultSet when it is closed.
0693: */
0694: if (SanityManager.DEBUG) {
0695: if (SanityManager.DEBUG_ON("ScanTrace")) {
0696: //traceClose();
0697: }
0698: }
0699:
0700: // we don't want to keep around a pointer to the
0701: // row ... so it can be thrown away.
0702: // REVISIT: does this need to be in a finally
0703: // block, to ensure that it is executed?
0704: clearCurrentRow();
0705: ;
0706: if (scanController != null) {
0707: // This is where we get the positioner info for inner tables
0708: if (runTimeStatisticsOn) {
0709: // This is where we get the scan properties for a subquery
0710: scanProperties = getScanProperties();
0711: startPositionString = printStartPosition();
0712: stopPositionString = printStopPosition();
0713: }
0714: scanController.close();
0715: scanController = null; // should not access after close
0716: activation.clearIndexScanInfo();
0717: }
0718: scanControllerOpened = false;
0719: startPosition = null;
0720: stopPosition = null;
0721:
0722: super .close();
0723:
0724: if (indexCols != null) {
0725: ConglomerateController borrowedBaseCC = activation
0726: .getHeapConglomerateController();
0727: if (borrowedBaseCC != null) {
0728: borrowedBaseCC.close();
0729: activation.clearHeapConglomerateController();
0730: }
0731: }
0732: if (futureRowResultSet != null)
0733: futureRowResultSet.close();
0734: } else if (SanityManager.DEBUG)
0735: SanityManager.DEBUG("CloseRepeatInfo",
0736: "Close of TableScanResultSet repeated");
0737:
0738: closeTime += getElapsedMillis(beginTime);
0739: }
0740:
0741: /**
0742: * Return the total amount of time spent in this ResultSet
0743: *
0744: * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
0745: * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
0746: *
0747: * @return long The total amount of time spent (in milliseconds).
0748: */
0749: public long getTimeSpent(int type) {
0750: long totTime = constructorTime + openTime + nextTime
0751: + closeTime;
0752:
0753: /* RESOLVE - subtract out store time later, when available */
0754: if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY) {
0755: return totTime;
0756: } else {
0757: return totTime;
0758: }
0759: }
0760:
0761: //
0762: // CursorResultSet interface
0763: //
0764:
0765: /**
0766: * This result set has its row location from
0767: * the last fetch done. If the cursor is closed,
0768: * or the row has been deleted a null is returned.
0769: *
0770: * @see CursorResultSet
0771: *
0772: * @return the row location of the current cursor row.
0773: * @exception StandardException thrown on failure to get row location
0774: */
0775: public RowLocation getRowLocation() throws StandardException {
0776: RowLocation rl;
0777:
0778: if (!isOpen)
0779: return null;
0780:
0781: if (!scanControllerOpened)
0782: return null;
0783:
0784: /*
0785: ** If the conglomerate is keyed, the row location of the base row
0786: ** is in the last column of the current row. If it's not keyed,
0787: ** we get the row location from the scan of the heap.
0788: */
0789: if (isKeyed) {
0790: if (SanityManager.DEBUG) {
0791: SanityManager
0792: .ASSERT(currentRow != null,
0793: "There must be a current row when fetching the row location");
0794: }
0795:
0796: rl = (RowLocation) currentRow.getColumn(currentRow
0797: .nColumns());
0798: } else {
0799: if (currentRowIsValid) {
0800: // we reuse the same rowlocation object across several calls.
0801: if (rlTemplate == null)
0802: rlTemplate = scanController
0803: .newRowLocationTemplate();
0804: rl = rlTemplate;
0805: try {
0806: scanController.fetchLocation(rl);
0807: } catch (StandardException se) {
0808: if (se.getMessageId().equals(
0809: SQLState.HEAP_SCAN_NOT_POSITIONED)) {
0810: //Have a easier to understand error message than what
0811: //we get from store
0812: throw StandardException
0813: .newException(SQLState.NO_CURRENT_ROW);
0814: }
0815: throw se;
0816: }
0817: } else {
0818: rl = null;
0819: }
0820: }
0821:
0822: return rl;
0823: }
0824:
0825: /**
0826: * This result set has its row from the last fetch done.
0827: * If the cursor is closed, the row has been deleted, or
0828: * no longer qualifies (for forward only result sets) a
0829: * null is returned.
0830: *
0831: * @see CursorResultSet
0832: *
0833: * @return the last row returned;
0834: * @exception StandardException thrown on failure.
0835: */
0836: /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
0837: * once there is such a method. (currentRow is redundant)
0838: */
0839: public ExecRow getCurrentRow() throws StandardException {
0840: ExecRow result = null;
0841:
0842: if (SanityManager.DEBUG)
0843: SanityManager.ASSERT(isOpen, "TSRS expected to be open");
0844:
0845: if (currentRowPrescanned)
0846: return currentRow;
0847:
0848: /* Nothing to do if we're not currently on a row or
0849: * if the current row get deleted out from under us
0850: * or if there is no current scan (can happen if the
0851: * scan is being skipped) or if the current position
0852: * no longer qualifies.
0853: */
0854: try {
0855: if ((currentRow == null)
0856: || (!currentRowIsValid)
0857: || (!scanControllerOpened)
0858: || (qualify && scanController
0859: .isCurrentPositionDeleted())
0860: || (qualify && (!scanController
0861: .doesCurrentPositionQualify()))) {
0862: return null;
0863: }
0864: } catch (StandardException se) {
0865: if (se.getMessageId().equals(
0866: SQLState.AM_SCAN_NOT_POSITIONED)) {
0867: //bug 4515 - Have a easier to understand error message than what we get from store
0868: se = StandardException
0869: .newException(SQLState.NO_CURRENT_ROW);
0870: throw se;
0871: }
0872: }
0873:
0874: result = (ExecRow) resultRowAllocator.invoke(activation);
0875: currentRow = getCompactRow(result, accessedCols,
0876: (FormatableBitSet) null, isKeyed);
0877:
0878: try {
0879: scanController.fetchWithoutQualify(result.getRowArray());
0880: } catch (StandardException se) {
0881: if (se.getMessageId().equals(SQLState.AM_RECORD_NOT_FOUND)) {
0882: // Somehow the row got deleted between the above
0883: // doesCurrentPositionQualify() call and here (one way is if
0884: // this scan is read uncommitted isolation level).
0885: return null;
0886: } else {
0887: throw se;
0888: }
0889: }
0890:
0891: setCurrentRow(result);
0892: return currentRow;
0893: }
0894:
0895: /**
0896: * @see NoPutResultSet#positionScanAtRowLocation
0897: *
0898: * Also sets qualify to false so that later calls to getCurrentRow
0899: * will not attempt to re-qualify the current row.
0900: */
0901: public void positionScanAtRowLocation(RowLocation rl)
0902: throws StandardException {
0903: // Check if the scanController is a B-tree scan controller. Do not
0904: // attempt to re-position a b-tree controller.
0905: if (!isKeyed) {
0906: currentRowIsValid = scanController
0907: .positionAtRowLocation(rl);
0908: }
0909: qualify = false;
0910: scanRepositioned = true;
0911: }
0912:
0913: /**
0914: * Print the parameters that constructed this result set to the
0915: * trace stream.
0916: */
0917: /*
0918: private final void traceScanParameters()
0919: {
0920: if (SanityManager.DEBUG)
0921: {
0922: HeaderPrintWriter traceStream = SanityManager.GET_DEBUG_STREAM();
0923:
0924: traceStream.println("");
0925: traceStream.println("TableScanResultSet number " +
0926: resultSetNumber +
0927: " parameters:");
0928:
0929: traceStream.println("");
0930: traceStream.println("\tTable name: " + tableName);
0931: if (indexName != null)
0932: {
0933: traceStream.println("\tIndex name: " + indexName);
0934: }
0935: traceStream.println("");
0936: traceStream.println("\tStart position is: ");
0937: tracePrintPosition(traceStream,
0938: startSearchOperator,
0939: startKeyGetter);
0940: traceStream.println("");
0941: traceStream.println("\tStop position is: " );
0942: tracePrintPosition(traceStream,
0943: stopSearchOperator,
0944: stopKeyGetter);
0945: traceStream.println("");
0946: traceStream.println("\tQualifiers are: ");
0947: tracePrintQualifiers(traceStream, qualifiers, 2);
0948: traceStream.println("");
0949: }
0950: }
0951: */
0952:
0953: /**
0954: * Print I/O statistics about a scan when it closes.
0955: */
0956: /*
0957: private final void traceClose()
0958: {
0959: if (SanityManager.DEBUG)
0960: {
0961: InfoStreams infoStreams;
0962: HeaderPrintWriter traceStream;
0963:
0964: traceStream = SanityManager.GET_DEBUG_STREAM();
0965:
0966: traceStream.println("TableScanResultSet number " +
0967: resultSetNumber +
0968: " closed.");
0969: if (isKeyed)
0970: {
0971: traceStream.println("\t" +
0972: rowCount() +
0973: " row(s) qualified from " +
0974: "keyed" +
0975: " table " +
0976: tableName +
0977: " using index " +
0978: indexName);
0979: }
0980: else
0981: {
0982: traceStream.println("\t" +
0983: rowCount() +
0984: " row(s) qualified from " +
0985: "non-keyed" +
0986: " table " +
0987: tableName);
0988: }
0989: traceStream.println("");
0990: }
0991: }
0992: */
0993:
0994: /**
0995: * Print a start or stop positioner to the trace stream.
0996: */
0997: /*
0998: private final void tracePrintPosition(HeaderPrintWriter traceStream,
0999: int searchOperator,
1000: GeneratedMethod positionGetter)
1001: {
1002: if (SanityManager.DEBUG)
1003: {
1004: if (positionGetter == null)
1005: {
1006: traceStream.println("\t\tNone");
1007: return;
1008: }
1009:
1010: ExecIndexRow positioner = null;
1011:
1012: try
1013: {
1014: positioner = (ExecIndexRow) positionGetter.invoke(activation);
1015: }
1016: catch (StandardException e)
1017: {
1018: traceStream.println("\t\tUnexpected exception " +
1019: e +
1020: " getting positioner.");
1021: e.printStackTrace(traceStream.getPrintWriter());
1022: return;
1023: }
1024:
1025: if (positioner == null)
1026: {
1027: traceStream.println("\t\tNone");
1028: return;
1029: }
1030:
1031: String searchOp = null;
1032:
1033: switch (searchOperator)
1034: {
1035: case ScanController.GE:
1036: searchOp = "GE";
1037: break;
1038:
1039: case ScanController.GT:
1040: searchOp = "GT";
1041: break;
1042:
1043: default:
1044: searchOp = "unknown value (" + searchOperator + ")";
1045: break;
1046: }
1047:
1048: traceStream.println("\t\t" +
1049: searchOp +
1050: " on first " +
1051: positioner.nColumns() +
1052: " column(s).");
1053:
1054: traceStream.print(
1055: "\t\tOrdered null semantics on the following columns: ");
1056: for (int position = 0; position < positioner.nColumns(); position++)
1057: {
1058: if (positioner.areNullsOrdered(position))
1059: {
1060: traceStream.print(position + " ");
1061: }
1062: }
1063: traceStream.println("");
1064: }
1065: }
1066: */
1067:
1068: /**
1069: * Print an array of Qualifiers to the trace stream.
1070: */
1071: /*
1072: private final void tracePrintQualifiers(HeaderPrintWriter traceStream,
1073: Qualifier[][] qualifiers,
1074: int depth)
1075: {
1076: if (SanityManager.DEBUG)
1077: {
1078: char[] indentchars = new char[depth];
1079:
1080: /*
1081: ** Form an array of tab characters for indentation.
1082: *
1083: while (depth > 0)
1084: {
1085: indentchars[depth - 1] = '\t';
1086: depth--;
1087: }
1088: String indent = new String(indentchars);
1089:
1090: if (qualifiers == null)
1091: {
1092: traceStream.println(indent +
1093: MessageService.getTextMessage(
1094: SQLState.LANG_NONE)
1095: );
1096: return;
1097: }
1098:
1099: // RESOLVE (mikem) We don't support 2-d qualifiers yet.
1100: if (SanityManager.DEBUG)
1101: {
1102: SanityManager.ASSERT(qualifiers.length == 1);
1103: }
1104:
1105: for (int i = 0; i < qualifiers[0].length; i++)
1106: {
1107: Qualifier qual = qualifiers[0][i];
1108:
1109: traceStream.println("");
1110: traceStream.println(indent + "Column Id: " + qual.getColumnId());
1111:
1112: int operator = qual.getOperator();
1113: String opString = null;
1114: switch (operator)
1115: {
1116: case Orderable.ORDER_OP_EQUALS:
1117: opString = "=";
1118: break;
1119:
1120: case Orderable.ORDER_OP_LESSOREQUALS:
1121: opString = "<=";
1122: break;
1123:
1124: case Orderable.ORDER_OP_LESSTHAN:
1125: opString = "<";
1126: break;
1127:
1128: default:
1129: opString = "unknown value (" + operator + ")";
1130: break;
1131: }
1132: traceStream.println(indent + "Operator: " + opString);
1133: traceStream.println(indent + "Ordered nulls: " +
1134: qual.getOrderedNulls());
1135: traceStream.println(indent + "Unknown return value: " +
1136: qual.getUnknownRV());
1137: traceStream.println(indent + "Negate comparison result: " +
1138: qual.negateCompareResult());
1139: traceStream.println("");
1140: }
1141: }
1142: }
1143: */
1144:
1145: public String printStartPosition() {
1146: return printPosition(startSearchOperator, startKeyGetter,
1147: startPosition);
1148: }
1149:
1150: public String printStopPosition() {
1151: if (sameStartStopPosition) {
1152: return printPosition(stopSearchOperator, startKeyGetter,
1153: startPosition);
1154: } else {
1155: return printPosition(stopSearchOperator, stopKeyGetter,
1156: stopPosition);
1157: }
1158: }
1159:
1160: /**
1161: * Return a start or stop positioner as a String.
1162: *
1163: * If we already generated the information, then use
1164: * that. Otherwise, invoke the activation to get it.
1165: */
1166: private String printPosition(int searchOperator,
1167: GeneratedMethod positionGetter, ExecIndexRow positioner) {
1168: String idt = "";
1169: String output = "";
1170: if (positionGetter == null) {
1171: return "\t"
1172: + MessageService.getTextMessage(SQLState.LANG_NONE)
1173: + "\n";
1174: }
1175:
1176: if (positioner == null) {
1177: try {
1178: positioner = (ExecIndexRow) positionGetter
1179: .invoke(activation);
1180: } catch (StandardException e) {
1181: // the positionGetter will fail with a NullPointerException
1182: // if the outer table is empty
1183: // (this isn't a problem since we won't call it on the inner
1184: // table if there are no rows on the outer table)
1185: if (e.getSQLState() == SQLState.LANG_UNEXPECTED_USER_EXCEPTION)
1186: return "\t"
1187: + MessageService
1188: .getTextMessage(SQLState.LANG_POSITION_NOT_AVAIL);
1189: return "\t"
1190: + MessageService
1191: .getTextMessage(
1192: SQLState.LANG_UNEXPECTED_EXC_GETTING_POSITIONER,
1193: e.toString());
1194: }
1195: }
1196: if (positioner == null) {
1197: return "\t"
1198: + MessageService.getTextMessage(SQLState.LANG_NONE)
1199: + "\n";
1200: }
1201: String searchOp = null;
1202:
1203: switch (searchOperator) {
1204: case ScanController.GE:
1205: searchOp = ">=";
1206: break;
1207:
1208: case ScanController.GT:
1209: searchOp = ">";
1210: break;
1211:
1212: default:
1213: if (SanityManager.DEBUG) {
1214: SanityManager.THROWASSERT("Unknown search operator "
1215: + searchOperator);
1216: }
1217:
1218: // NOTE: This does not have to be internationalized because
1219: // this code should never be reached.
1220: searchOp = "unknown value (" + searchOperator + ")";
1221: break;
1222: }
1223:
1224: output = output
1225: + "\t"
1226: + MessageService.getTextMessage(
1227: SQLState.LANG_POSITIONER, searchOp, String
1228: .valueOf(positioner.nColumns())) + "\n";
1229:
1230: output = output
1231: + "\t"
1232: + MessageService
1233: .getTextMessage(SQLState.LANG_ORDERED_NULL_SEMANTICS)
1234: + "\n";
1235: for (int position = 0; position < positioner.nColumns(); position++) {
1236: if (positioner.areNullsOrdered(position)) {
1237: output = output + position + " ";
1238: }
1239: }
1240:
1241: return output + "\n";
1242: }
1243:
1244: public Properties getScanProperties() {
1245: if (scanProperties == null) {
1246: scanProperties = new Properties();
1247: }
1248: try {
1249: if (scanController != null) {
1250: scanController.getScanInfo().getAllScanInfo(
1251: scanProperties);
1252: /* Did we get a coarser lock due to
1253: * a covering lock, lock escalation
1254: * or configuration?
1255: */
1256: coarserLock = scanController.isTableLocked()
1257: && (lockMode == TransactionController.MODE_RECORD);
1258: }
1259: } catch (StandardException se) {
1260: // ignore
1261: }
1262:
1263: return scanProperties;
1264: }
1265:
1266: /**
1267: * @see NoPutResultSet#getScanIsolationLevel
1268: */
1269: public int getScanIsolationLevel() {
1270: return isolationLevel;
1271: }
1272:
1273: /**
1274: * @see NoPutResultSet#requiresRelocking
1275: */
1276: public boolean requiresRelocking() {
1277: return (isolationLevel == TransactionController.ISOLATION_READ_COMMITTED_NOHOLDLOCK);
1278: }
1279:
1280: /**
1281: * Update the number of rows in the scan controller.
1282: *
1283: * NOTE: It would be more efficient to only update the
1284: * scan controller if the optimizer's estimated number of
1285: * rows were wrong by more than some threshold (like 10%).
1286: * This would require a little more work than I have the
1287: * time for now, however, as the row estimate that is given
1288: * to this result set is the total number of rows for all
1289: * scans, not the number of rows per scan.
1290: *
1291: *
1292: * @param rowsThisScan The number of rows to update the scanController to
1293: *
1294: * @exception StandardException Thrown on error
1295: */
1296: protected final void setRowCountIfPossible(long rowsThisScan)
1297: throws StandardException {
1298: /*
1299: ** Is it a heap scan with no qualifiers (full table scan?)
1300: ** and is it not for update (we don't want to count rows we're
1301: ** about to delete.
1302: */
1303: if ((!scanController.isKeyed())
1304: && (qualifiers == null || qualifiers.length == 0)
1305: && (!forUpdate)) {
1306:
1307: // Only update rows if different by more than 10%
1308: long diff = rowsThisScan - estimatedRowCount;
1309:
1310: long tenPerCent = estimatedRowCount / 10;
1311:
1312: if (diff < 0)
1313: diff = -diff;
1314:
1315: if (diff > tenPerCent)
1316: scanController.setEstimatedRowCount(rowsThisScan);
1317: }
1318: }
1319:
1320: /**
1321: * Can we get instantaneous locks when getting share row
1322: * locks at READ COMMITTED.
1323: */
1324: protected boolean canGetInstantaneousLocks() {
1325: return false;
1326: }
1327:
1328: /**
1329: * Is this ResultSet or it's source result set for update
1330: *
1331: * @return Whether or not the result set is for update.
1332: */
1333: public boolean isForUpdate() {
1334: return forUpdate;
1335: }
1336:
1337: /**
1338: * Shallow clone this result set. Used in trigger reference.
1339: * beetle 4373.
1340: */
1341: public Object clone() {
1342: Object clo = null;
1343: try {
1344: clo = super .clone();
1345: } catch (CloneNotSupportedException e) {
1346: }
1347: return clo;
1348: }
1349: }
|