0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.sql.execute.ScrollInsensitiveResultSet
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.services.loader.GeneratedMethod;
0025:
0026: import org.apache.derby.iapi.services.monitor.Monitor;
0027:
0028: import org.apache.derby.iapi.services.sanity.SanityManager;
0029:
0030: import org.apache.derby.iapi.sql.execute.CursorResultSet;
0031: import org.apache.derby.iapi.sql.execute.ExecRow;
0032: import org.apache.derby.iapi.sql.execute.ExecutionContext;
0033: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
0034:
0035: import org.apache.derby.iapi.sql.Activation;
0036: import org.apache.derby.iapi.sql.Row;
0037:
0038: import org.apache.derby.iapi.store.access.ConglomerateController;
0039: import org.apache.derby.iapi.store.access.ScanController;
0040: import org.apache.derby.iapi.store.access.TransactionController;
0041:
0042: import org.apache.derby.iapi.types.RowLocation;
0043: import org.apache.derby.iapi.types.DataValueDescriptor;
0044:
0045: import org.apache.derby.iapi.error.StandardException;
0046: import org.apache.derby.iapi.reference.SQLState;
0047:
0048: import org.apache.derby.iapi.store.access.BackingStoreHashtable;
0049: import org.apache.derby.iapi.services.io.FormatableBitSet;
0050:
0051: import org.apache.derby.iapi.types.SQLBoolean;
0052: import org.apache.derby.iapi.types.SQLInteger;
0053:
0054: /**
0055: *
0056: * Provide insensitive scrolling functionality for the underlying
0057: * result set. We build a disk backed hash table of rows as the
0058: * user scrolls forward, with the position as the key.
0059: *
0060: * For read-only result sets the hash table will containg the
0061: * following columns:
0062: *<pre>
0063: * +-------------------------------+
0064: * | KEY |
0065: * +-------------------------------+
0066: * | Row |
0067: * +-------------------------------+
0068: *</pre>
0069: * where key is the position of the row in the result set and row is the data.
0070: *
0071: * And for updatable result sets it will contain:
0072: * <pre>
0073: * +-------------------------------+
0074: * | KEY | [0]
0075: * +-------------------------------+
0076: * | RowLocation | [POS_ROWLOCATION]
0077: * +-------------------------------+
0078: * | Deleted | [POS_ROWDELETED]
0079: * +-------------------------------+
0080: * | Updated | [POS_ROWUPDATED]
0081: * +-------------------------------+
0082: * | Row | [extraColumns ... n]
0083: * +-------------------------------+
0084: *</pre>
0085: * where key is the position of the row in the result set, rowLocation is
0086: * the row location of that row in the Heap, Deleted indicates whether the
0087: * row has been deleted, Updated indicates whether the row has been updated,
0088: * and row is the data.
0089: *
0090: */
0091:
0092: public class ScrollInsensitiveResultSet extends NoPutResultSetImpl
0093: implements CursorResultSet {
0094: /*
0095: ** Set in constructor and not altered during life of object.
0096: */
0097:
0098: public NoPutResultSet source;
0099:
0100: private int sourceRowWidth;
0101:
0102: private BackingStoreHashtable ht;
0103: private ExecRow resultRow;
0104:
0105: // Scroll tracking
0106: private int positionInSource;
0107: private int currentPosition;
0108: private int lastPosition;
0109: private boolean seenFirst;
0110: private boolean seenLast;
0111: private boolean beforeFirst = true;
0112: private boolean afterLast;
0113:
0114: public int numFromHashTable;
0115: public int numToHashTable;
0116:
0117: private int maxRows;
0118:
0119: private boolean keepAfterCommit;
0120:
0121: /* The hash table will contain a different number of extra columns depending
0122: * on whether the result set is updatable or not.
0123: * extraColumns will contain the number of extra columns on the hash table,
0124: * 1 for read-only result sets and LAST_EXTRA_COLUMN + 1 for updatable
0125: * result sets.
0126: */
0127: private int extraColumns;
0128:
0129: /* positionInHashTable is used for getting a row from the hash table. Prior
0130: * to getting the row, positionInHashTable will be set to the desired KEY.
0131: */
0132: private SQLInteger positionInHashTable;
0133:
0134: /* Reference to the target result set. Target is used for updatable result
0135: * sets in order to keep the target result set on the same row as the
0136: * ScrollInsensitiveResultSet.
0137: */
0138: private CursorResultSet target;
0139:
0140: /* If the last row was fetched from the HashTable, updatable result sets
0141: * need to be positioned in the last fetched row before resuming the
0142: * fetch from core.
0143: */
0144: private boolean needsRepositioning;
0145:
0146: /* Position of the different fields in the hash table row for updatable
0147: * result sets
0148: */
0149: private static final int POS_ROWLOCATION = 1;
0150: private static final int POS_ROWDELETED = 2;
0151: private static final int POS_ROWUPDATED = 3;
0152: private static final int LAST_EXTRA_COLUMN = 3;
0153:
0154: /**
0155: * Constructor for a ScrollInsensitiveResultSet
0156: *
0157: * @param source The NoPutResultSet from which to get rows
0158: * to scroll through
0159: * @param activation The activation for this execution
0160: * @param resultSetNumber The resultSetNumber
0161: * @param sourceRowWidth # of columns in the source row
0162: *
0163: * @exception StandardException on error
0164: */
0165:
0166: public ScrollInsensitiveResultSet(NoPutResultSet source,
0167: Activation activation, int resultSetNumber,
0168: int sourceRowWidth, double optimizerEstimatedRowCount,
0169: double optimizerEstimatedCost) throws StandardException {
0170: super (activation, resultSetNumber, optimizerEstimatedRowCount,
0171: optimizerEstimatedCost);
0172: this .source = source;
0173: this .sourceRowWidth = sourceRowWidth;
0174: keepAfterCommit = activation.getResultSetHoldability();
0175: maxRows = activation.getMaxRows();
0176: if (SanityManager.DEBUG) {
0177: SanityManager.ASSERT(maxRows != -1,
0178: "maxRows not expected to be -1");
0179: }
0180:
0181: constructorTime += getElapsedMillis(beginTime);
0182:
0183: positionInHashTable = new SQLInteger();
0184: needsRepositioning = false;
0185: if (isForUpdate()) {
0186: target = ((CursorActivation) activation)
0187: .getTargetResultSet();
0188: extraColumns = LAST_EXTRA_COLUMN + 1;
0189: } else {
0190: target = null;
0191: extraColumns = 1;
0192: }
0193: }
0194:
0195: //
0196: // ResultSet interface (leftover from NoPutResultSet)
0197: //
0198:
0199: /**
0200: * open a scan on the source. scan parameters are evaluated
0201: * at each open, so there is probably some way of altering
0202: * their values...
0203: *
0204: * @exception StandardException thrown on failure
0205: */
0206: public void openCore() throws StandardException {
0207: beginTime = getCurrentTimeMillis();
0208: if (SanityManager.DEBUG)
0209: SanityManager.ASSERT(!isOpen,
0210: "ScrollInsensitiveResultSet already open");
0211:
0212: source.openCore();
0213: isOpen = true;
0214: numOpens++;
0215:
0216: /* Create the hash table. We pass
0217: * null in as the row source as we will
0218: * build the hash table on demand as
0219: * the user scrolls.
0220: * The 1st column, the position in the
0221: * scan, will be the key column.
0222: */
0223: final int[] keyCols = new int[] { 0 };
0224:
0225: /* We don't use the optimizer row count for this because it could be
0226: * wildly pessimistic. We only use Hash tables when the optimizer row count
0227: * is within certain bounds. We have no alternative for scrolling insensitive
0228: * cursors so we'll just trust that it will fit.
0229: * We need BackingStoreHashtable to actually go to disk when it doesn't fit.
0230: * This is a known limitation.
0231: */
0232: ht = new BackingStoreHashtable(getTransactionController(),
0233: null,
0234: keyCols,
0235: false,
0236: -1, // don't trust optimizer row count
0237: HashScanResultSet.DEFAULT_MAX_CAPACITY,
0238: HashScanResultSet.DEFAULT_INITIAL_CAPACITY,
0239: HashScanResultSet.DEFAULT_MAX_CAPACITY, false,
0240: keepAfterCommit);
0241:
0242: openTime += getElapsedMillis(beginTime);
0243: setBeforeFirstRow();
0244: }
0245:
0246: /**
0247: * reopen a scan on the table. scan parameters are evaluated
0248: * at each open, so there is probably some way of altering
0249: * their values...
0250: *
0251: * @exception StandardException thrown if cursor finished.
0252: */
0253: public void reopenCore() throws StandardException {
0254: boolean constantEval = true;
0255:
0256: beginTime = getCurrentTimeMillis();
0257:
0258: if (SanityManager.DEBUG) {
0259: SanityManager.ASSERT(isOpen,
0260: "ScrollInsensitiveResultSet already open");
0261: SanityManager
0262: .THROWASSERT("reopenCore() not expected to be called");
0263: }
0264: setBeforeFirstRow();
0265: }
0266:
0267: /**
0268: * Returns the row at the absolute position from the query,
0269: * and returns NULL when there is no such position.
0270: * (Negative position means from the end of the result set.)
0271: * Moving the cursor to an invalid position leaves the cursor
0272: * positioned either before the first row (negative position)
0273: * or after the last row (positive position).
0274: * NOTE: An exception will be thrown on 0.
0275: *
0276: * @param row The position.
0277: * @return The row at the absolute position, or NULL if no such position.
0278: *
0279: * @exception StandardException Thrown on failure
0280: * @see Row
0281: */
0282: public ExecRow getAbsoluteRow(int row) throws StandardException {
0283: if (!isOpen) {
0284: throw StandardException.newException(
0285: SQLState.LANG_RESULT_SET_NOT_OPEN, "absolute");
0286: }
0287:
0288: attachStatementContext();
0289:
0290: if (SanityManager.DEBUG) {
0291: if (!isTopResultSet) {
0292: SanityManager.THROWASSERT(this
0293: + "expected to be the top ResultSet");
0294: }
0295: }
0296:
0297: // Absolute 0 is defined to be before first!
0298: if (row == 0) {
0299: setBeforeFirstRow();
0300: return null;
0301: }
0302:
0303: if (seenLast && row > lastPosition) {
0304: return setAfterLastRow();
0305: }
0306:
0307: if (row > 0) {
0308: // position is from the start of the result set
0309: if (row <= positionInSource) {
0310: // We've already seen the row before
0311: return getRowFromHashTable(row);
0312: }
0313:
0314: /* We haven't seen the row yet, scan until we find
0315: * it or we get to the end.
0316: */
0317: int diff = row - positionInSource;
0318: ExecRow result = null;
0319: while (diff > 0) {
0320: if ((result = getNextRowFromSource()) != null) {
0321: diff--;
0322: } else {
0323: break;
0324: }
0325: }
0326: if (result != null) {
0327: result = getRowFromHashTable(row);
0328: }
0329: currentRow = result;
0330: return result;
0331: } else if (row < 0) {
0332: // position is from the end of the result set
0333:
0334: // Get the last row, if we haven't already
0335: if (!seenLast) {
0336: getLastRow();
0337: }
0338:
0339: // Note, for negative values position is from beyond the end
0340: // of the result set, e.g. absolute(-1) points to the last row
0341: int beyondResult = lastPosition + 1;
0342: if (beyondResult + row > 0) {
0343: // valid row
0344: return getRowFromHashTable(beyondResult + row);
0345: } else {
0346: // position before the beginning of the result set
0347: return setBeforeFirstRow();
0348: }
0349: }
0350:
0351: currentRow = null;
0352: return null;
0353: }
0354:
0355: /**
0356: * Returns the row at the relative position from the current
0357: * cursor position, and returns NULL when there is no such position.
0358: * (Negative position means toward the beginning of the result set.)
0359: * Moving the cursor to an invalid position leaves the cursor
0360: * positioned either before the first row (negative position)
0361: * or after the last row (positive position).
0362: * NOTE: 0 is valid.
0363: * NOTE: An exception is thrown if the cursor is not currently
0364: * positioned on a row.
0365: *
0366: * @param row The position.
0367: * @return The row at the relative position, or NULL if no such position.
0368: *
0369: * @exception StandardException Thrown on failure
0370: * @see Row
0371: */
0372: public ExecRow getRelativeRow(int row) throws StandardException {
0373: if (!isOpen) {
0374: throw StandardException.newException(
0375: SQLState.LANG_RESULT_SET_NOT_OPEN, "relative");
0376: }
0377:
0378: attachStatementContext();
0379:
0380: if (SanityManager.DEBUG) {
0381: if (!isTopResultSet) {
0382: SanityManager.THROWASSERT(this
0383: + "expected to be the top ResultSet");
0384: }
0385: }
0386:
0387: // Return the current row for 0
0388: if (row == 0) {
0389: if (beforeFirst || afterLast || currentPosition == 0) {
0390: return null;
0391: } else {
0392: return getRowFromHashTable(currentPosition);
0393: }
0394: } else if (row > 0) {
0395: return getAbsoluteRow(currentPosition + row);
0396: } else {
0397: // row < 0
0398: if (currentPosition + row < 0) {
0399: return setBeforeFirstRow();
0400: }
0401: return getAbsoluteRow(currentPosition + row);
0402: }
0403: }
0404:
0405: /**
0406: * Sets the current position to before the first row and returns NULL
0407: * because there is no current row.
0408: *
0409: * @return NULL.
0410: *
0411: * @see Row
0412: */
0413: public ExecRow setBeforeFirstRow() {
0414: currentPosition = 0;
0415: beforeFirst = true;
0416: afterLast = false;
0417: currentRow = null;
0418: return null;
0419: }
0420:
0421: /**
0422: * Returns the first row from the query, and returns NULL when there
0423: * are no rows.
0424: *
0425: * @return The first row, or NULL if no rows.
0426: *
0427: * @exception StandardException Thrown on failure
0428: * @see Row
0429: */
0430: public ExecRow getFirstRow() throws StandardException {
0431: if (!isOpen) {
0432: throw StandardException.newException(
0433: SQLState.LANG_RESULT_SET_NOT_OPEN, "first");
0434: }
0435:
0436: /* Get the row from the hash table if
0437: * we have already seen it before.
0438: */
0439: if (seenFirst) {
0440: return getRowFromHashTable(1);
0441: }
0442:
0443: attachStatementContext();
0444:
0445: if (SanityManager.DEBUG) {
0446: if (!isTopResultSet) {
0447: SanityManager.THROWASSERT(this
0448: + "expected to be the top ResultSet");
0449: }
0450: }
0451:
0452: return getNextRowCore();
0453: }
0454:
0455: /**
0456: *
0457: * @exception StandardException thrown on failure
0458: */
0459: public ExecRow getNextRowCore() throws StandardException {
0460: ExecRow result = null;
0461:
0462: beginTime = getCurrentTimeMillis();
0463: if (!isOpen)
0464: throw StandardException.newException(
0465: SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
0466:
0467: if (seenLast && currentPosition == lastPosition) {
0468: return setAfterLastRow();
0469: }
0470:
0471: /* Should we get the next row from the source or the hash table? */
0472: if (currentPosition == positionInSource) {
0473: /* Current position is same as position in source.
0474: * Get row from the source.
0475: */
0476: result = getNextRowFromSource();
0477: if (result != null) {
0478: result = getRowFromHashTable(currentPosition);
0479: }
0480: } else if (currentPosition < positionInSource) {
0481: /* Current position is before position in source.
0482: * Get row from the hash table.
0483: */
0484: result = getRowFromHashTable(currentPosition + 1);
0485: } else {
0486: result = null;
0487: }
0488:
0489: if (result != null) {
0490: rowsSeen++;
0491: afterLast = false;
0492: }
0493:
0494: currentRow = result;
0495: setCurrentRow(currentRow);
0496: beforeFirst = false;
0497:
0498: nextTime += getElapsedMillis(beginTime);
0499:
0500: return result;
0501: }
0502:
0503: /**
0504: * Returns the previous row from the query, and returns NULL when there
0505: * are no more previous rows.
0506: *
0507: * @return The previous row, or NULL if no more previous rows.
0508: *
0509: * @exception StandardException Thrown on failure
0510: * @see Row
0511: */
0512: public ExecRow getPreviousRow() throws StandardException {
0513: if (!isOpen) {
0514: throw StandardException.newException(
0515: SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
0516: }
0517:
0518: if (SanityManager.DEBUG) {
0519: if (!isTopResultSet) {
0520: SanityManager.THROWASSERT(this
0521: + "expected to be the top ResultSet");
0522: }
0523: }
0524:
0525: /* No row if we are positioned before the first row
0526: * or the result set is empty.
0527: */
0528: if (beforeFirst || currentPosition == 0) {
0529: currentRow = null;
0530: return null;
0531: }
0532:
0533: // Get the last row, if we are after it
0534: if (afterLast) {
0535: // Special case for empty tables
0536: if (lastPosition == 0) {
0537: afterLast = false;
0538: beforeFirst = false;
0539: currentRow = null;
0540: return null;
0541: } else {
0542: return getRowFromHashTable(lastPosition);
0543: }
0544: }
0545:
0546: // Move back 1
0547: currentPosition--;
0548: if (currentPosition == 0) {
0549: setBeforeFirstRow();
0550: return null;
0551: }
0552: return getRowFromHashTable(currentPosition);
0553: }
0554:
0555: /**
0556: * Returns the last row from the query, and returns NULL when there
0557: * are no rows.
0558: *
0559: * @return The last row, or NULL if no rows.
0560: *
0561: * @exception StandardException Thrown on failure
0562: * @see Row
0563: */
0564: public ExecRow getLastRow() throws StandardException {
0565: if (!isOpen) {
0566: throw StandardException.newException(
0567: SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
0568: }
0569:
0570: if (!seenLast) {
0571: attachStatementContext();
0572:
0573: if (SanityManager.DEBUG) {
0574: if (!isTopResultSet) {
0575: SanityManager.THROWASSERT(this
0576: + "expected to be the top ResultSet");
0577: }
0578: }
0579:
0580: /* Scroll to the end, filling the hash table as
0581: * we scroll, and return the last row that we find.
0582: */
0583: ExecRow result = null;
0584: while ((result = getNextRowFromSource()) != null)
0585: ;
0586: }
0587:
0588: if (SanityManager.DEBUG && !seenLast) {
0589: SanityManager.THROWASSERT(this
0590: + "expected to have seen last");
0591: }
0592:
0593: beforeFirst = false;
0594: afterLast = false;
0595:
0596: // Special case if table is empty
0597: if (lastPosition == 0) {
0598: currentRow = null;
0599: return null;
0600: } else {
0601: return getRowFromHashTable(lastPosition);
0602: }
0603: }
0604:
0605: /**
0606: * Sets the current position to after the last row and returns NULL
0607: * because there is no current row.
0608: *
0609: * @return NULL.
0610: *
0611: * @exception StandardException Thrown on failure
0612: * @see Row
0613: */
0614: public ExecRow setAfterLastRow() throws StandardException {
0615: if (!seenLast) {
0616: getLastRow();
0617: }
0618: if (lastPosition == 0) {
0619: // empty rs special case
0620: currentPosition = 0;
0621: afterLast = false;
0622: } else {
0623: currentPosition = lastPosition + 1;
0624: afterLast = true;
0625: }
0626:
0627: beforeFirst = false;
0628: currentRow = null;
0629: return null;
0630: }
0631:
0632: /**
0633: * Determine if the cursor is before the first row in the result
0634: * set.
0635: *
0636: * @return true if before the first row, false otherwise. Returns
0637: * false when the result set contains no rows.
0638: * @exception StandardException Thrown on error.
0639: */
0640: public boolean checkRowPosition(int isType)
0641: throws StandardException {
0642: switch (isType) {
0643: case ISBEFOREFIRST:
0644:
0645: if (!beforeFirst) {
0646: return false;
0647: }
0648:
0649: // Spec says to return false if result set is empty
0650: if (seenFirst) {
0651: return true;
0652: } else {
0653: ExecRow firstRow = getFirstRow();
0654: if (firstRow == null) {
0655: // ResultSet is empty
0656: return false;
0657: } else {
0658: // ResultSet is not empty - reset position
0659: getPreviousRow();
0660: return true;
0661: }
0662: }
0663: case ISFIRST:
0664: return (currentPosition == 1);
0665: case ISLAST:
0666: if (beforeFirst || afterLast || currentPosition == 0
0667: || currentPosition < positionInSource) {
0668: return false;
0669: }
0670:
0671: /* If we have seen the last row, we can tell if we are
0672: * on it by comparing currentPosition with lastPosition.
0673: * Otherwise, we check if there is a next row.
0674: */
0675: if (seenLast) {
0676: return (currentPosition == lastPosition);
0677: } else {
0678: final int savePosition = currentPosition;
0679: final boolean retval = (getNextRowFromSource() == null);
0680: getRowFromHashTable(savePosition);
0681: return retval;
0682: }
0683: case ISAFTERLAST:
0684: return afterLast;
0685: default:
0686: return false;
0687: }
0688: }
0689:
0690: /**
0691: * Returns the row number of the current row. Row
0692: * numbers start from 1 and go to 'n'. Corresponds
0693: * to row numbering used to position current row
0694: * in the result set (as per JDBC).
0695: *
0696: * @return the row number, or 0 if not on a row
0697: *
0698: */
0699: public int getRowNumber() {
0700: return currentRow == null ? 0 : currentPosition;
0701: }
0702:
0703: /* Get the next row from the source ResultSet tree and insert into the hash table */
0704: private ExecRow getNextRowFromSource() throws StandardException {
0705: ExecRow sourceRow = null;
0706: ExecRow result = null;
0707:
0708: /* Don't give back more rows than requested */
0709: if (maxRows > 0 && maxRows == positionInSource) {
0710: seenLast = true;
0711: lastPosition = positionInSource;
0712: afterLast = true;
0713: return null;
0714: }
0715:
0716: if (needsRepositioning) {
0717: positionInLastFetchedRow();
0718: needsRepositioning = false;
0719: }
0720: sourceRow = source.getNextRowCore();
0721:
0722: if (sourceRow != null) {
0723: seenFirst = true;
0724: beforeFirst = false;
0725:
0726: long beginTCTime = getCurrentTimeMillis();
0727: /* If this is the first row from the source then we create a new row
0728: * for use when fetching from the hash table.
0729: */
0730: if (resultRow == null) {
0731: resultRow = activation.getExecutionFactory()
0732: .getValueRow(sourceRowWidth);
0733: }
0734:
0735: positionInSource++;
0736: currentPosition = positionInSource;
0737:
0738: RowLocation rowLoc = null;
0739: if (source.isForUpdate()) {
0740: rowLoc = ((CursorResultSet) source).getRowLocation();
0741: }
0742:
0743: addRowToHashTable(sourceRow, currentPosition, rowLoc, false);
0744:
0745: }
0746: // Remember whether or not we're past the end of the table
0747: else {
0748: if (!seenLast) {
0749: lastPosition = positionInSource;
0750: }
0751: seenLast = true;
0752: // Special case for empty table (afterLast is never true)
0753: if (positionInSource == 0) {
0754: afterLast = false;
0755: } else {
0756: afterLast = true;
0757: currentPosition = positionInSource + 1;
0758: }
0759: }
0760:
0761: return sourceRow;
0762: }
0763:
0764: /**
0765: * If the result set has been opened,
0766: * close the open scan.
0767: *
0768: * @exception StandardException thrown on error
0769: */
0770: public void close() throws StandardException {
0771: beginTime = getCurrentTimeMillis();
0772: if (isOpen) {
0773: currentRow = null;
0774: source.close();
0775:
0776: if (ht != null) {
0777: ht.close();
0778: ht = null;
0779: }
0780:
0781: super .close();
0782: } else if (SanityManager.DEBUG)
0783: SanityManager.DEBUG("CloseRepeatInfo",
0784: "Close of ScrollInsensitiveResultSet repeated");
0785: setBeforeFirstRow();
0786:
0787: closeTime += getElapsedMillis(beginTime);
0788: }
0789:
0790: public void finish() throws StandardException {
0791: source.finish();
0792: finishAndRTS();
0793: }
0794:
0795: /**
0796: * Return the total amount of time spent in this ResultSet
0797: *
0798: * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
0799: * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
0800: *
0801: * @return long The total amount of time spent (in milliseconds).
0802: */
0803: public long getTimeSpent(int type) {
0804: long totTime = constructorTime + openTime + nextTime
0805: + closeTime;
0806:
0807: if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY) {
0808: return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);
0809: } else {
0810: return totTime;
0811: }
0812: }
0813:
0814: //
0815: // CursorResultSet interface
0816: //
0817:
0818: /**
0819: * Gets information from its source. We might want
0820: * to have this take a CursorResultSet in its constructor some day,
0821: * instead of doing a cast here?
0822: *
0823: * @see CursorResultSet
0824: *
0825: * @return the row location of the current cursor row.
0826: *
0827: * @exception StandardException thrown on failure
0828: */
0829: public RowLocation getRowLocation() throws StandardException {
0830: if (SanityManager.DEBUG)
0831: SanityManager.ASSERT(source instanceof CursorResultSet,
0832: "source not CursorResultSet");
0833: return ((CursorResultSet) source).getRowLocation();
0834: }
0835:
0836: /**
0837: * Gets information from last getNextRow call.
0838: *
0839: * @see CursorResultSet
0840: *
0841: * @return the last row returned.
0842: */
0843: /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
0844: * once there is such a method. (currentRow is redundant)
0845: */
0846: public ExecRow getCurrentRow() throws StandardException {
0847: if (isForUpdate() && isDeleted()) {
0848: return null;
0849: } else {
0850: return currentRow;
0851: }
0852: }
0853:
0854: //
0855: // class implementation
0856: //
0857:
0858: /**
0859: * Add a row to the backing hash table, keyed on position.
0860: * When a row gets updated when using scrollable insensitive updatable
0861: * result sets, the old entry for the row will be deleted from the hash
0862: * table and this method will be called to add the new values for the row
0863: * to the hash table, with the parameter rowUpdated = true so as to mark
0864: * the row as updated. The latter is done in order to implement
0865: * detectability of own changes for result sets of this type.
0866: *
0867: * @param sourceRow The row to add.
0868: * @param position The key
0869: * @param rowLoc The rowLocation of the row to add.
0870: * @param rowUpdated Indicates whether the row has been updated.
0871: *
0872: */
0873: private void addRowToHashTable(ExecRow sourceRow, int position,
0874: RowLocation rowLoc, boolean rowUpdated)
0875: throws StandardException {
0876: DataValueDescriptor[] hashRowArray = new DataValueDescriptor[sourceRowWidth
0877: + extraColumns];
0878: // 1st element is the key
0879: hashRowArray[0] = new SQLInteger(position);
0880: if (isForUpdate()) {
0881: hashRowArray[POS_ROWLOCATION] = rowLoc.getClone();
0882: hashRowArray[POS_ROWDELETED] = new SQLBoolean(false);
0883: hashRowArray[POS_ROWUPDATED] = new SQLBoolean(rowUpdated);
0884: }
0885:
0886: /* Copy rest of elements from sourceRow.
0887: * NOTE: We need to clone the source row
0888: * and we do our own cloning since the 1st column
0889: * is not a wrapper.
0890: */
0891: DataValueDescriptor[] sourceRowArray = sourceRow.getRowArray();
0892:
0893: System.arraycopy(sourceRowArray, 0, hashRowArray, extraColumns,
0894: sourceRowArray.length);
0895:
0896: ht.put(true, hashRowArray);
0897:
0898: numToHashTable++;
0899: }
0900:
0901: /**
0902: * Get the row at the specified position
0903: * from the hash table.
0904: *
0905: * @param position The specified position.
0906: *
0907: * @return The row at that position.
0908: *
0909: * @exception StandardException thrown on failure
0910: */
0911: private ExecRow getRowFromHashTable(int position)
0912: throws StandardException {
0913:
0914: // Get the row from the hash table
0915: positionInHashTable.setValue(position);
0916: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
0917: .get(positionInHashTable);
0918:
0919: if (SanityManager.DEBUG) {
0920: SanityManager.ASSERT(hashRowArray != null,
0921: "hashRowArray expected to be non-null");
0922: }
0923: // Copy out the Object[] without the position.
0924: DataValueDescriptor[] resultRowArray = new DataValueDescriptor[hashRowArray.length
0925: - extraColumns];
0926: System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0,
0927: resultRowArray.length);
0928:
0929: resultRow.setRowArray(resultRowArray);
0930:
0931: // Reset the current position to the user position
0932: currentPosition = position;
0933:
0934: numFromHashTable++;
0935:
0936: if (resultRow != null) {
0937: beforeFirst = false;
0938: afterLast = false;
0939: }
0940:
0941: if (isForUpdate()) {
0942: RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
0943: // Keep source and target with the same currentRow
0944: ((NoPutResultSet) target).setCurrentRow(resultRow);
0945: ((NoPutResultSet) target).positionScanAtRowLocation(rowLoc);
0946: needsRepositioning = true;
0947: }
0948:
0949: setCurrentRow(resultRow);
0950:
0951: return resultRow;
0952: }
0953:
0954: /**
0955: * Get the row data at the specified position
0956: * from the hash table.
0957: *
0958: * @param position The specified position.
0959: *
0960: * @return The row data at that position.
0961: *
0962: * @exception StandardException thrown on failure
0963: */
0964: private DataValueDescriptor[] getRowArrayFromHashTable(int position)
0965: throws StandardException {
0966: positionInHashTable.setValue(position);
0967: final DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
0968: .get(positionInHashTable);
0969:
0970: // Copy out the Object[] without the position.
0971: final DataValueDescriptor[] resultRowArray = new DataValueDescriptor[hashRowArray.length
0972: - extraColumns];
0973: System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0,
0974: resultRowArray.length);
0975: return resultRowArray;
0976: }
0977:
0978: /**
0979: * Positions the cursor in the last fetched row. This is done before
0980: * navigating to a row that has not previously been fetched, so that
0981: * getNextRowCore() will re-start from where it stopped.
0982: */
0983: private void positionInLastFetchedRow() throws StandardException {
0984: if (positionInSource > 0) {
0985: positionInHashTable.setValue(positionInSource);
0986: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
0987: .get(positionInHashTable);
0988: RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
0989: ((NoPutResultSet) target).positionScanAtRowLocation(rowLoc);
0990: currentPosition = positionInSource;
0991: }
0992: }
0993:
0994: /**
0995: * @see NoPutResultSet#updateRow
0996: *
0997: * Sets the updated column of the hash table to true and updates the row
0998: * in the hash table with the new values for the row.
0999: */
1000: public void updateRow(ExecRow row) throws StandardException {
1001: ExecRow newRow = row;
1002: boolean undoProjection = false;
1003:
1004: if (source instanceof ProjectRestrictResultSet) {
1005: newRow = ((ProjectRestrictResultSet) source)
1006: .doBaseRowProjection(row);
1007: undoProjection = true;
1008: }
1009: positionInHashTable.setValue(currentPosition);
1010: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
1011: .get(positionInHashTable);
1012: RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1013: ht.remove(new SQLInteger(currentPosition));
1014: addRowToHashTable(newRow, currentPosition, rowLoc, true);
1015:
1016: // Modify row to refer to data in the BackingStoreHashtable.
1017: // This allows reading of data which goes over multiple pages
1018: // when doing the actual update (LOBs). Putting columns of
1019: // type SQLBinary to disk, has destructive effect on the columns,
1020: // and they need to be re-read. That is the reason this is needed.
1021: if (undoProjection) {
1022:
1023: final DataValueDescriptor[] newRowData = newRow
1024: .getRowArray();
1025:
1026: // Array of original position in row
1027: final int[] origPos = ((ProjectRestrictResultSet) source)
1028: .getBaseProjectMapping();
1029:
1030: // We want the row to contain data backed in BackingStoreHashtable
1031: final DataValueDescriptor[] backedData = getRowArrayFromHashTable(currentPosition);
1032:
1033: for (int i = 0; i < origPos.length; i++) {
1034: if (origPos[i] >= 0) {
1035: row.setColumn(origPos[i], backedData[i]);
1036: }
1037: }
1038: } else {
1039: row.setRowArray(getRowArrayFromHashTable(currentPosition));
1040: }
1041: }
1042:
1043: /**
1044: * @see NoPutResultSet#markRowAsDeleted
1045: *
1046: * Sets the deleted column of the hash table to true in the current row.
1047: */
1048: public void markRowAsDeleted() throws StandardException {
1049: positionInHashTable.setValue(currentPosition);
1050: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
1051: .get(positionInHashTable);
1052: RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1053: ht.remove(new SQLInteger(currentPosition));
1054: ((SQLBoolean) hashRowArray[POS_ROWDELETED]).setValue(true);
1055: // Set all columns to NULL, the row is now a placeholder
1056: for (int i = extraColumns; i < hashRowArray.length; i++) {
1057: hashRowArray[i].setToNull();
1058: }
1059:
1060: ht.put(true, hashRowArray);
1061: }
1062:
1063: /**
1064: * Returns TRUE if the row was been deleted within the transaction,
1065: * otherwise returns FALSE
1066: *
1067: * @return True if the row has been deleted, otherwise false
1068: *
1069: * @exception StandardException on error
1070: */
1071: public boolean isDeleted() throws StandardException {
1072: if (currentPosition <= positionInSource && currentPosition > 0) {
1073: positionInHashTable.setValue(currentPosition);
1074: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
1075: .get(positionInHashTable);
1076: return hashRowArray[POS_ROWDELETED].getBoolean();
1077: }
1078: return false;
1079: }
1080:
1081: /**
1082: * Returns TRUE if the row was been updated within the transaction,
1083: * otherwise returns FALSE
1084: *
1085: * @return True if the row has been deleted, otherwise false
1086: *
1087: * @exception StandardException on error
1088: */
1089: public boolean isUpdated() throws StandardException {
1090: if (currentPosition <= positionInSource && currentPosition > 0) {
1091: positionInHashTable.setValue(currentPosition);
1092: DataValueDescriptor[] hashRowArray = (DataValueDescriptor[]) ht
1093: .get(positionInHashTable);
1094: return hashRowArray[POS_ROWUPDATED].getBoolean();
1095: }
1096: return false;
1097: }
1098:
1099: public boolean isForUpdate() {
1100: return source.isForUpdate();
1101: }
1102:
1103: }
|