0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.sql.execute.UpdateResultSet
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: import org.apache.derby.iapi.services.monitor.Monitor;
0026: import org.apache.derby.iapi.services.sanity.SanityManager;
0027: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
0028: import org.apache.derby.iapi.services.stream.InfoStreams;
0029: import org.apache.derby.iapi.services.io.StreamStorable;
0030: import org.apache.derby.iapi.error.StandardException;
0031: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
0032: import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
0033: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
0034: import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
0035: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
0036: import org.apache.derby.iapi.types.BooleanDataValue;
0037: import org.apache.derby.iapi.types.DataValueDescriptor;
0038: import org.apache.derby.iapi.types.RowLocation;
0039: import org.apache.derby.iapi.sql.execute.ConstantAction;
0040: import org.apache.derby.iapi.sql.execute.CursorResultSet;
0041: import org.apache.derby.iapi.sql.execute.ExecRow;
0042: import org.apache.derby.iapi.sql.execute.ExecutionContext;
0043: import org.apache.derby.iapi.sql.execute.RowChanger;
0044: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
0045:
0046: import org.apache.derby.iapi.types.DataValueDescriptor;
0047: import org.apache.derby.iapi.sql.Activation;
0048: import org.apache.derby.iapi.sql.ResultDescription;
0049: import org.apache.derby.iapi.sql.ResultSet;
0050:
0051: import org.apache.derby.iapi.store.access.ConglomerateController;
0052: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
0053: import org.apache.derby.iapi.store.access.ScanController;
0054: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
0055: import org.apache.derby.iapi.store.access.TransactionController;
0056:
0057: import org.apache.derby.iapi.reference.SQLState;
0058:
0059: import org.apache.derby.iapi.db.TriggerExecutionContext;
0060: import org.apache.derby.iapi.services.io.FormatableBitSet;
0061: import java.util.Properties;
0062: import java.util.Hashtable;
0063:
0064: /**
0065: * Update the rows from the specified
0066: * base table. This will cause constraints to be checked
0067: * and triggers to be executed based on the c's and t's
0068: * compiled into the update plan.
0069: *
0070: * @author ames
0071: */
0072: class UpdateResultSet extends DMLWriteResultSet {
0073: private TransactionController tc;
0074: private ExecRow newBaseRow;
0075: private ExecRow row;
0076: private ExecRow deferredSparseRow;
0077: UpdateConstantAction constants;
0078:
0079: private ResultDescription resultDescription;
0080: private NoPutResultSet source;
0081: NoPutResultSet savedSource;
0082: private RowChanger rowChanger;
0083:
0084: protected ConglomerateController deferredBaseCC;
0085:
0086: protected long[] deferredUniqueCIDs;
0087: protected boolean[] deferredUniqueCreated;
0088: protected ConglomerateController deferredUniqueCC[];
0089: protected ScanController[] deferredUniqueScans;
0090:
0091: private TemporaryRowHolderImpl deletedRowHolder;
0092: private TemporaryRowHolderImpl insertedRowHolder;
0093:
0094: // cached
0095: private RISetChecker riChecker;
0096: private TriggerInfo triggerInfo;
0097: private TriggerEventActivator triggerActivator;
0098: private boolean updatingReferencedKey;
0099: private boolean updatingForeignKey;
0100: private int numOpens;
0101: private long heapConglom;
0102: private FKInfo[] fkInfoArray;
0103: private FormatableBitSet baseRowReadList;
0104: private GeneratedMethod checkGM;
0105: private int resultWidth;
0106: private int numberOfBaseColumns;
0107: private ExecRow deferredTempRow;
0108: private ExecRow deferredBaseRow;
0109: private ExecRow oldDeletedRow;
0110: private ResultDescription triggerResultDescription;
0111:
0112: int lockMode;
0113: boolean deferred;
0114: boolean beforeUpdateCopyRequired = false;
0115:
0116: /**
0117: * Returns the description of the updated rows.
0118: * REVISIT: Do we want this to return NULL instead?
0119: */
0120: public ResultDescription getResultDescription() {
0121: return resultDescription;
0122: }
0123:
0124: /*
0125: * class interface
0126: *
0127: */
0128: /**
0129: * @param source update rows come from source
0130: * @param checkGM Generated method for enforcing check constraints
0131: * @exception StandardException thrown on error
0132: */
0133: UpdateResultSet(NoPutResultSet source, GeneratedMethod checkGM,
0134: Activation activation) throws StandardException {
0135: this (source, checkGM, activation, activation
0136: .getConstantAction(), null);
0137: }
0138:
0139: /*
0140: * class interface
0141: *
0142: */
0143: /**
0144: * @param source update rows come from source
0145: * @param checkGM Generated method for enforcing check constraints
0146: * @param activation Activation
0147: * @param constantActionItem id of the update constant action saved objec
0148: * @param rsdItem id of the Result Description saved object
0149: * @exception StandardException thrown on error
0150: */
0151: UpdateResultSet(NoPutResultSet source, GeneratedMethod checkGM,
0152: Activation activation, int constantActionItem, int rsdItem)
0153: throws StandardException {
0154: this (source, checkGM, activation, ((ConstantAction) activation
0155: .getPreparedStatement().getSavedObject(
0156: constantActionItem)),
0157: (ResultDescription) activation.getPreparedStatement()
0158: .getSavedObject(rsdItem));
0159:
0160: // In case of referential action update, we do a deferred updates
0161: deferred = true;
0162: }
0163:
0164: /*
0165: * class interface
0166: *
0167: */
0168: /**
0169: * @param source update rows come from source
0170: * @param checkGM Generated method for enforcing check constraints
0171: * @exception StandardException thrown on error
0172: */
0173: UpdateResultSet(NoPutResultSet source, GeneratedMethod checkGM,
0174: Activation activation,
0175: ConstantAction passedInConstantAction,
0176: ResultDescription passedInRsd) throws StandardException {
0177: super (activation, passedInConstantAction);
0178:
0179: // Get the current transaction controller
0180: tc = activation.getTransactionController();
0181: this .source = source;
0182: this .checkGM = checkGM;
0183:
0184: constants = (UpdateConstantAction) constantAction;
0185: fkInfoArray = constants.getFKInfo(lcc.getExecutionContext());
0186: triggerInfo = constants.getTriggerInfo(lcc
0187: .getExecutionContext());
0188:
0189: heapConglom = constants.conglomId;
0190:
0191: baseRowReadList = constants.getBaseRowReadList();
0192: if (passedInRsd == null)
0193: resultDescription = source.getResultDescription();
0194: else
0195: resultDescription = passedInRsd;
0196: /*
0197: ** We NEED a result description when we are going to
0198: ** to have to kick off a trigger. In a replicated environment
0199: ** we don't get a result description when we are replaying
0200: ** source xacts on the target, which should never be the
0201: ** case for an UpdateResultSet.
0202: */
0203: if (SanityManager.DEBUG) {
0204: if (resultDescription == null) {
0205: SanityManager
0206: .ASSERT(triggerInfo == null,
0207: "triggers need a result description to pass to result sets given to users");
0208: }
0209: }
0210:
0211: if (fkInfoArray != null) {
0212: for (int i = 0; i < fkInfoArray.length; i++) {
0213: if (fkInfoArray[i].type == FKInfo.REFERENCED_KEY) {
0214: updatingReferencedKey = true;
0215: if (SanityManager.DEBUG) {
0216: SanityManager
0217: .ASSERT(constants.deferred,
0218: "updating referenced key but update not deferred, wuzzup?");
0219: }
0220: } else {
0221: updatingForeignKey = true;
0222: }
0223: }
0224: }
0225:
0226: /* Get the # of columns in the ResultSet */
0227: resultWidth = resultDescription.getColumnCount();
0228:
0229: /*
0230: ** Calculate the # of columns in the base table. The result set
0231: ** contains the before columns, the after columns, and the RowLocation,
0232: ** so the number of base columns is half of the number of result set
0233: ** columns, after subtracting one for the row location column.
0234: */
0235: numberOfBaseColumns = (resultWidth - 1) / 2;
0236:
0237: /* Get the new base row */
0238: newBaseRow = RowUtil.getEmptyValueRow(numberOfBaseColumns, lcc);
0239:
0240: /* decode lock mode */
0241: lockMode = decodeLockMode(lcc, constants.lockMode);
0242: deferred = constants.deferred;
0243:
0244: //update can be marked for deferred mode because the scan is being done
0245: //using index. But it is not necesary to keep the before copy
0246: //of the row in the temporary row holder (deletedRowHolder) unless
0247: //there are RI constraint or Triggers.(beetle:5301)
0248: if (triggerInfo != null || fkInfoArray != null) {
0249: beforeUpdateCopyRequired = true;
0250: }
0251:
0252: }
0253:
0254: /**
0255: @exception StandardException Standard Cloudscape error policy
0256: */
0257: public void open() throws StandardException {
0258:
0259: setup();
0260: collectAffectedRows();
0261:
0262: /*
0263: ** If this is a deferred update, read the new rows and RowLocations
0264: ** from the temporary conglomerate and update the base table using
0265: ** the RowChanger.
0266: */
0267: if (deferred) {
0268:
0269: runChecker(true); //check for only RESTRICT referential action rule violations
0270: fireBeforeTriggers();
0271: updateDeferredRows();
0272: /* Apply deferred inserts to unique indexes */
0273: rowChanger.finish();
0274: runChecker(false); //check for all violations
0275: fireAfterTriggers();
0276:
0277: } else {
0278: /* Apply deferred inserts to unique indexes */
0279: rowChanger.finish();
0280: }
0281:
0282: cleanUp();
0283: }
0284:
0285: /**
0286: @exception StandardException Standard Cloudscape error policy
0287: */
0288: void setup() throws StandardException {
0289: boolean firstOpen = (rowChanger == null);
0290:
0291: rowCount = 0;
0292:
0293: /* Cache query plan text for source, before it gets blown away */
0294: if (lcc.getRunTimeStatisticsMode()) {
0295: /* savedSource nulled after run time statistics generation */
0296: savedSource = source;
0297: }
0298:
0299: /* Get or re-use the row changer.
0300: * NOTE: We need to set ourself as the top result set
0301: * if this is not the 1st execution. (Done in constructor
0302: * for 1st execution.)
0303: */
0304: if (firstOpen) {
0305: rowChanger = lcc.getLanguageConnectionFactory()
0306: .getExecutionFactory().getRowChanger(heapConglom,
0307: constants.heapSCOCI, heapDCOCI,
0308: constants.irgs, constants.indexCIDS,
0309: constants.indexSCOCIs, indexDCOCIs,
0310: constants.numColumns, tc,
0311: constants.changedColumnIds,
0312: constants.getBaseRowReadList(),
0313: constants.getBaseRowReadMap(),
0314: constants.getStreamStorableHeapColIds(),
0315: activation);
0316: rowChanger.setIndexNames(constants.indexNames);
0317: } else {
0318: lcc.getStatementContext().setTopResultSet(this ,
0319: subqueryTrackingArray);
0320: }
0321:
0322: /* Open the RowChanger before the source ResultSet so that
0323: * the store will see the RowChanger's lock as a covering lock
0324: * if it is a table lock.
0325: */
0326: rowChanger.open(lockMode);
0327:
0328: if (numOpens++ == 0) {
0329: source.openCore();
0330: } else {
0331: source.reopenCore();
0332: }
0333:
0334: /* The source does not know whether or not we are doing a
0335: * deferred mode update. If we are, then we must clear the
0336: * index scan info from the activation so that the row changer
0337: * does not re-use that information (which won't be valid for
0338: * a deferred mode update).
0339: */
0340: if (deferred) {
0341: activation.clearIndexScanInfo();
0342: }
0343:
0344: if (fkInfoArray != null) {
0345: if (riChecker == null) {
0346: riChecker = new RISetChecker(tc, fkInfoArray);
0347: } else {
0348: riChecker.reopen();
0349: }
0350: }
0351:
0352: if (deferred) {
0353: /* Allocate the temporary rows and get result description
0354: * if this is the 1st time that we are executing.
0355: */
0356: if (firstOpen) {
0357: deferredTempRow = RowUtil.getEmptyValueRow(
0358: numberOfBaseColumns + 1, lcc);
0359: oldDeletedRow = RowUtil.getEmptyValueRow(
0360: numberOfBaseColumns, lcc);
0361: triggerResultDescription = (resultDescription != null) ? resultDescription
0362: .truncateColumns(numberOfBaseColumns + 1)
0363: : null;
0364: }
0365:
0366: Properties properties = new Properties();
0367:
0368: // Get the properties on the heap
0369: rowChanger.getHeapConglomerateController()
0370: .getInternalTablePropertySet(properties);
0371: if (beforeUpdateCopyRequired) {
0372: deletedRowHolder = new TemporaryRowHolderImpl(
0373: activation, properties,
0374: triggerResultDescription);
0375: }
0376: insertedRowHolder = new TemporaryRowHolderImpl(activation,
0377: properties, triggerResultDescription);
0378:
0379: rowChanger.setRowHolder(insertedRowHolder);
0380: }
0381:
0382: }
0383:
0384: /* Following 2 methods are for checking and make sure we don't have one un-objectified stream
0385: * to be inserted into 2 temp table rows for deferred update. Otherwise it would cause problem
0386: * when writing to disk using the stream a second time. In other cases we don't want to
0387: * unnecessarily objectify the stream. beetle 4896.
0388: */
0389: private FormatableBitSet checkStreamCols() {
0390: DataValueDescriptor[] cols = row.getRowArray();
0391: FormatableBitSet streamCols = null;
0392: for (int i = 0; i < numberOfBaseColumns; i++) {
0393: if (cols[i + numberOfBaseColumns] instanceof StreamStorable) //check new values
0394: {
0395: if (streamCols == null)
0396: streamCols = new FormatableBitSet(
0397: numberOfBaseColumns);
0398: streamCols.set(i);
0399: }
0400: }
0401: return streamCols;
0402: }
0403:
0404: private void objectifyStream(ExecRow tempRow,
0405: FormatableBitSet streamCols) throws StandardException {
0406: DataValueDescriptor[] cols = tempRow.getRowArray();
0407: for (int i = 0; i < numberOfBaseColumns; i++) {
0408: if (cols[i] != null && streamCols.get(i))
0409: ((StreamStorable) cols[i]).loadStream();
0410: }
0411: }
0412:
0413: public boolean collectAffectedRows() throws StandardException {
0414:
0415: boolean rowsFound = false;
0416: row = getNextRowCore(source);
0417: if (row != null)
0418: rowsFound = true;
0419: else {
0420: activation.addWarning(StandardException
0421: .newWarning(SQLState.LANG_NO_ROW_FOUND));
0422: }
0423:
0424: //beetle 3865, update cursor use index.
0425: TableScanResultSet tableScan = (TableScanResultSet) activation
0426: .getForUpdateIndexScan();
0427: boolean notifyCursor = ((tableScan != null) && !tableScan.sourceDrained);
0428: boolean checkStream = (deferred && rowsFound && !constants.singleRowSource);
0429: FormatableBitSet streamCols = (checkStream ? checkStreamCols()
0430: : null);
0431: checkStream = (streamCols != null);
0432:
0433: while (row != null) {
0434:
0435: /* By convention, the last column in the result set for an
0436: * update contains a SQLRef containing the RowLocation of
0437: * the row to be updated.
0438: */
0439:
0440: /*
0441: ** If we're doing deferred update, write the new row and row
0442: ** location to the temporary conglomerate. If we're not doing
0443: ** deferred update, update the permanent conglomerates now
0444: ** using the RowChanger.
0445: */
0446: if (deferred) {
0447: /*
0448: ** If we have a before trigger, we must evaluate the
0449: ** check constraint after we have executed the trigger.
0450: ** Note that we have compiled checkGM accordingly (to
0451: ** handle the different row shape if we are evaluating
0452: ** against the input result set or a temporary row holder
0453: ** result set).
0454: */
0455: if (triggerInfo == null) {
0456: evaluateCheckConstraints(checkGM, activation);
0457: }
0458:
0459: /*
0460: ** We are going to only save off the updated
0461: ** columns and the RID. For a trigger, all columns
0462: ** were marked as needed so we'll copy them all.
0463: */
0464: RowUtil.copyRefColumns(deferredTempRow, row,
0465: numberOfBaseColumns, numberOfBaseColumns + 1);
0466: if (checkStream)
0467: objectifyStream(deferredTempRow, streamCols);
0468:
0469: insertedRowHolder.insert(deferredTempRow);
0470:
0471: /*
0472: ** Grab a copy of the row to delete. We are
0473: ** going to use this for deferred RI checks.
0474: */
0475: if (beforeUpdateCopyRequired) {
0476: RowUtil.copyRefColumns(oldDeletedRow, row,
0477: numberOfBaseColumns);
0478:
0479: deletedRowHolder.insert(oldDeletedRow);
0480: }
0481:
0482: /*
0483: ** If we haven't already, lets get a template to
0484: ** use as a template for our rescan of the base table.
0485: ** Do this now while we have a real row to use
0486: ** as a copy.
0487: **
0488: ** There is one less column in the base row than
0489: ** there is in source row, because the base row
0490: ** doesn't contain the row location.
0491: */
0492: if (deferredBaseRow == null) {
0493: deferredBaseRow = RowUtil.getEmptyValueRow(
0494: numberOfBaseColumns, lcc);
0495:
0496: RowUtil.copyCloneColumns(deferredBaseRow, row,
0497: numberOfBaseColumns);
0498:
0499: /*
0500: ** While we're here, let's also create a sparse row for
0501: ** fetching from the store.
0502: */
0503: deferredSparseRow = makeDeferredSparseRow(
0504: deferredBaseRow, baseRowReadList, lcc);
0505: }
0506: } else {
0507: evaluateCheckConstraints(checkGM, activation);
0508:
0509: /* Get the RowLocation to update
0510: * NOTE - Column #s in the Row are 1 based.
0511: */
0512: RowLocation baseRowLocation = (RowLocation) (row
0513: .getColumn(resultWidth)).getObject();
0514:
0515: RowUtil.copyRefColumns(newBaseRow, row,
0516: numberOfBaseColumns, numberOfBaseColumns);
0517:
0518: if (riChecker != null) {
0519: /*
0520: ** Make sure all foreign keys in the new row
0521: ** are maintained. Note that we don't bother
0522: ** checking primary/unique keys that are referenced
0523: ** here. The reason is that if we are updating
0524: ** a referenced key, we'll be updating in deferred
0525: ** mode, so we wont get here.
0526: */
0527: riChecker.doFKCheck(newBaseRow);
0528: }
0529:
0530: source.updateRow(newBaseRow);
0531: rowChanger.updateRow(row, newBaseRow, baseRowLocation);
0532:
0533: //beetle 3865, update cursor use index.
0534: if (notifyCursor)
0535: notifyForUpdateCursor(row.getRowArray(), newBaseRow
0536: .getRowArray(), baseRowLocation, tableScan);
0537: }
0538:
0539: rowCount++;
0540:
0541: // No need to do a next on a single row source
0542: if (constants.singleRowSource) {
0543: row = null;
0544: } else {
0545: row = getNextRowCore(source);
0546: }
0547: }
0548:
0549: return rowsFound;
0550: }
0551:
0552: /* beetle 3865, updateable cursor use index. If the row we are updating has new value that
0553: * falls into the direction of the index scan of the cursor, we save this rid into a hash table
0554: * (for fast search), so that when the cursor hits it again, it knows to skip it. When we get
0555: * to a point that the hash table is full, we scan forward the cursor until one of two things
0556: * happen: (1) we hit a record whose rid is in the hash table (we went through it already, so
0557: * skip it), we remove it from hash table, so that we can continue to use hash table. OR, (2) the scan
0558: * forward hit the end. If (2) happens, we can de-reference the hash table to make it available
0559: * for garbage collection. We save the future row id's in a virtual mem heap. In any case,
0560: * next read will use a row id that we saved.
0561: */
0562: private void notifyForUpdateCursor(DataValueDescriptor[] row,
0563: DataValueDescriptor[] newBaseRow, RowLocation rl,
0564: TableScanResultSet tableScan) throws StandardException {
0565: int[] indexCols = tableScan.indexCols;
0566: int[] changedCols = constants.changedColumnIds;
0567: boolean placedForward = false, ascending, decided = false, overlap = false;
0568: int basePos, k;
0569: /* first of all, we see if there's overlap between changed column ids and index key
0570: * columns. If so, we see if the new update value falls into the future range of the
0571: * index scan, if so, we need to save it in hash table.
0572: */
0573: for (int i = 0; i < indexCols.length; i++) {
0574: basePos = indexCols[i];
0575: if (basePos > 0)
0576: ascending = true;
0577: else {
0578: ascending = false;
0579: basePos = -basePos;
0580: }
0581: for (int j = 0; j < changedCols.length; j++) {
0582: if (basePos == changedCols[j]) {
0583: decided = true; //we pretty much decided if new row falls in front
0584: //of the cursor or behind
0585: /* the row and newBaseRow we get are compact base row that only have
0586: * referenced columns. Our "basePos" is index in sparse heap row, so
0587: * we need the BaseRowReadMap to map into the compact row.
0588: */
0589: int[] map = constants.getBaseRowReadMap();
0590: if (map == null)
0591: k = basePos - 1;
0592: else
0593: k = map[basePos - 1];
0594:
0595: DataValueDescriptor key;
0596: /* We need to compare with saved most-forward cursor scan key if we
0597: * are reading records from the saved RowLocation temp table (instead
0598: * of the old column value) because we only care if new update value
0599: * jumps forward the most-forward scan key.
0600: */
0601: if (tableScan.compareToLastKey)
0602: key = tableScan.lastCursorKey.getColumn(i + 1);
0603: else
0604: key = row[k];
0605:
0606: /* Starting from the first index key column forward, we see if the direction
0607: * of the update change is consistent with the direction of index scan.
0608: * If so, we save it in hash table.
0609: */
0610: if ((ascending && key.greaterThan(newBaseRow[k],
0611: key).equals(true))
0612: || (!ascending && key.lessThan(
0613: newBaseRow[k], key).equals(true)))
0614: placedForward = true;
0615: else if (key.equals(newBaseRow[k], key)
0616: .equals(true)) {
0617: decided = false;
0618: overlap = true;
0619: }
0620: break;
0621: }
0622: }
0623: if (decided) // already decided if new row falls in front or behind
0624: break;
0625: }
0626: /* If index row gets updated but key value didn't actually change, we still
0627: * put it in hash table because it can either fall in front or behind. This
0628: * can happen if the update explicitly sets a value, but same as old.
0629: */
0630: if (overlap && !decided)
0631: placedForward = true;
0632:
0633: if (placedForward) // add it to hash table
0634: {
0635: /* determining initial capacity of hash table from a few factors:
0636: * (1) user specified MAX_MEMORY_PER_TABLE property, (2) min value 100
0637: * (3) optimizer estimated row count. We want to avoid re-hashing if
0638: * possible, for performance reason, yet don't waste space. If initial
0639: * capacity is greater than max size divided by load factor, no rehash
0640: * is ever needed.
0641: */
0642: int maxCapacity = lcc.getOptimizerFactory()
0643: .getMaxMemoryPerTable() / 16;
0644: if (maxCapacity < 100)
0645: maxCapacity = 100;
0646:
0647: if (tableScan.past2FutureTbl == null) {
0648: double rowCount = tableScan.getEstimatedRowCount();
0649: int initCapacity = 32 * 1024;
0650: if (rowCount > 0.0) {
0651: rowCount = rowCount / 0.75 + 1.0; // load factor
0652: if (rowCount < initCapacity)
0653: initCapacity = (int) rowCount;
0654: }
0655: if (maxCapacity < initCapacity)
0656: initCapacity = maxCapacity;
0657:
0658: tableScan.past2FutureTbl = new Hashtable(initCapacity);
0659: }
0660:
0661: Hashtable past2FutureTbl = tableScan.past2FutureTbl;
0662: /* If hash table is not full, we add it in. The key of the hash entry
0663: * is the string value of the RowLocation. If the hash table is full,
0664: * as the comments above this function say, we scan forward.
0665: *
0666: * Need to save a clone because when we get cached currentRow, "rl" shares the
0667: * same reference, so is changed at the same time.
0668: */
0669: RowLocation updatedRL = (RowLocation) rl.getClone();
0670:
0671: if (past2FutureTbl.size() < maxCapacity)
0672: past2FutureTbl.put(updatedRL, updatedRL);
0673: else {
0674: tableScan.skipFutureRowHolder = true;
0675: ExecRow rlRow = new ValueRow(1);
0676:
0677: for (;;) {
0678: ExecRow aRow = tableScan.getNextRowCore();
0679: if (aRow == null) {
0680: tableScan.sourceDrained = true;
0681: tableScan.past2FutureTbl = null; // de-reference for garbage coll.
0682: break;
0683: }
0684: RowLocation rowLoc = (RowLocation) aRow
0685: .getColumn(aRow.nColumns());
0686:
0687: if (updatedRL.equals(rowLoc)) //this row we are updating jumped forward
0688: {
0689: saveLastCusorKey(tableScan, aRow);
0690: break; // don't need to worry about adding this row to hash any more
0691: }
0692:
0693: if (tableScan.futureForUpdateRows == null) {
0694: // virtual memory heap. In-memory part size 100. With the co-operation
0695: // of hash table and in-memory part of heap (hash table shrinks while
0696: // in-memory heap grows), hopefully we never spill temp table to disk.
0697:
0698: tableScan.futureForUpdateRows = new TemporaryRowHolderImpl(
0699: activation, null, null, 100, false,
0700: true);
0701: }
0702:
0703: rlRow.setColumn(1, rowLoc);
0704: tableScan.futureForUpdateRows.insert(rlRow);
0705: if (past2FutureTbl.size() < maxCapacity) //we got space in the hash table now, stop!
0706: {
0707: past2FutureTbl.put(updatedRL, updatedRL);
0708: saveLastCusorKey(tableScan, aRow);
0709: break;
0710: }
0711: }
0712: tableScan.skipFutureRowHolder = false;
0713: }
0714: }
0715: }
0716:
0717: private void saveLastCusorKey(TableScanResultSet tableScan,
0718: ExecRow aRow) throws StandardException {
0719: /* We save the most-forward cursor scan key where we are stopping, so
0720: * that next time when we decide if we need to put an updated row id into
0721: * hash table, we can compare with this key. This is an optimization on
0722: * memory usage of the hash table, otherwise it may be "leaking".
0723: */
0724: if (tableScan.lastCursorKey == null)
0725: tableScan.lastCursorKey = new ValueRow(aRow.nColumns() - 1);
0726: for (int i = 1; i <= tableScan.lastCursorKey.nColumns(); i++) {
0727: DataValueDescriptor aCol = aRow.getColumn(i);
0728: if (aCol != null)
0729: tableScan.lastCursorKey.setColumn(i, aCol.getClone());
0730: }
0731: }
0732:
0733: void fireBeforeTriggers() throws StandardException {
0734: if (deferred) {
0735: if (triggerInfo != null) {
0736: if (triggerActivator == null) {
0737: triggerActivator = new TriggerEventActivator(lcc,
0738: tc, constants.targetUUID, triggerInfo,
0739: TriggerExecutionContext.UPDATE_EVENT,
0740: activation, null);
0741: } else {
0742: triggerActivator.reopen();
0743: }
0744:
0745: // fire BEFORE trigger, do this before checking constraints
0746: triggerActivator.notifyEvent(
0747: TriggerEvents.BEFORE_UPDATE, deletedRowHolder
0748: .getResultSet(), insertedRowHolder
0749: .getResultSet());
0750:
0751: }
0752: }
0753: }
0754:
0755: void fireAfterTriggers() throws StandardException {
0756: if (deferred) {
0757: if (triggerActivator != null) {
0758: triggerActivator.notifyEvent(
0759: TriggerEvents.AFTER_UPDATE, deletedRowHolder
0760: .getResultSet(), insertedRowHolder
0761: .getResultSet());
0762: }
0763: }
0764: }
0765:
0766: void updateDeferredRows() throws StandardException {
0767: if (deferred) {
0768: // we already have everything locked
0769: deferredBaseCC = tc.openCompiledConglomerate(false,
0770: tc.OPENMODE_FORUPDATE
0771: | tc.OPENMODE_SECONDARY_LOCKED, lockMode,
0772: TransactionController.ISOLATION_SERIALIZABLE,
0773: constants.heapSCOCI, heapDCOCI);
0774:
0775: CursorResultSet rs = insertedRowHolder.getResultSet();
0776: try {
0777: /*
0778: ** We need to do a fetch doing a partial row
0779: ** read. We need to shift our 1-based bit
0780: ** set to a zero based bit set like the store
0781: ** expects.
0782: */
0783: FormatableBitSet readBitSet = RowUtil.shift(
0784: baseRowReadList, 1);
0785: ExecRow deferredTempRow2;
0786:
0787: rs.open();
0788: while ((deferredTempRow2 = rs.getNextRow()) != null) {
0789: /*
0790: ** Check the constraint now if we have triggers.
0791: ** Otherwise we evaluated them as we read the
0792: ** rows in from the source.
0793: */
0794: if (triggerInfo != null) {
0795: source.setCurrentRow(deferredTempRow);
0796: evaluateCheckConstraints(checkGM, activation);
0797: }
0798:
0799: /*
0800: ** The last column is a Ref, which contains a
0801: ** RowLocation.
0802: */
0803: DataValueDescriptor rlColumn = deferredTempRow2
0804: .getColumn(numberOfBaseColumns + 1);
0805: RowLocation baseRowLocation = (RowLocation) (rlColumn)
0806: .getObject();
0807:
0808: /* Get the base row at the given RowLocation */
0809: boolean row_exists = deferredBaseCC.fetch(
0810: baseRowLocation, deferredSparseRow
0811: .getRowArray(), readBitSet);
0812:
0813: if (SanityManager.DEBUG) {
0814: SanityManager
0815: .ASSERT(row_exists,
0816: "did not find base row in deferred update");
0817: }
0818:
0819: /*
0820: ** Copy the columns from the temp row to the base row.
0821: ** The base row has fewer columns than the temp row,
0822: ** because it doesn't contain the row location.
0823: */
0824: RowUtil.copyRefColumns(newBaseRow,
0825: deferredTempRow2, numberOfBaseColumns);
0826:
0827: rowChanger.updateRow(deferredBaseRow, newBaseRow,
0828: baseRowLocation);
0829: source.updateRow(newBaseRow);
0830: }
0831: } finally {
0832: source.clearCurrentRow();
0833: rs.close();
0834: }
0835: }
0836: }
0837:
0838: void runChecker(boolean restrictCheckOnly) throws StandardException {
0839:
0840: /*
0841: ** For a deferred update, make sure that there
0842: ** aren't any primary keys that were removed which
0843: ** are referenced.
0844: */
0845: if (deferred && updatingReferencedKey) {
0846: ExecRow deletedRow;
0847: CursorResultSet deletedRows;
0848:
0849: /*
0850: ** For each referenced key that was modified
0851: */
0852: for (int i = 0; i < fkInfoArray.length; i++) {
0853: if (fkInfoArray[i].type == FKInfo.FOREIGN_KEY) {
0854: continue;
0855: }
0856:
0857: deletedRows = deletedRowHolder.getResultSet();
0858: try {
0859: /*
0860: ** For each delete row
0861: */
0862: deletedRows.open();
0863: while ((deletedRow = deletedRows.getNextRow()) != null) {
0864: if (!foundRow(deletedRow,
0865: fkInfoArray[i].colArray,
0866: insertedRowHolder)) {
0867: riChecker.doRICheck(i, deletedRow,
0868: restrictCheckOnly);
0869: }
0870: }
0871: } finally {
0872: deletedRows.close();
0873: }
0874: }
0875: }
0876:
0877: /*
0878: ** For a deferred update, make sure that there
0879: ** aren't any foreign keys that were added that
0880: ** aren't referenced.
0881: */
0882: if (deferred && updatingForeignKey) {
0883: ExecRow insertedRow;
0884: CursorResultSet insertedRows;
0885:
0886: /*
0887: ** For each foreign key that was modified
0888: */
0889: for (int i = 0; i < fkInfoArray.length; i++) {
0890: if (fkInfoArray[i].type == FKInfo.REFERENCED_KEY) {
0891: continue;
0892: }
0893:
0894: insertedRows = insertedRowHolder.getResultSet();
0895: try {
0896: /*
0897: ** For each inserted row
0898: */
0899: insertedRows.open();
0900: while ((insertedRow = insertedRows.getNextRow()) != null) {
0901: if (!foundRow(insertedRow,
0902: fkInfoArray[i].colArray,
0903: deletedRowHolder)) {
0904: riChecker.doRICheck(i, insertedRow,
0905: restrictCheckOnly);
0906: }
0907: }
0908: } finally {
0909: insertedRows.close();
0910: }
0911: }
0912: }
0913:
0914: }
0915:
0916: public static boolean foundRow(ExecRow checkRow, int[] colsToCheck,
0917: TemporaryRowHolderImpl rowHolder) throws StandardException {
0918: ExecRow scanRow;
0919: boolean foundMatch = false;
0920: Object[] checkRowArray = checkRow.getRowArray();
0921: DataValueDescriptor checkCol;
0922: DataValueDescriptor scanCol;
0923:
0924: CursorResultSet rs = rowHolder.getResultSet();
0925: try {
0926: /*
0927: ** For each inserted row
0928: */
0929: rs.open();
0930: while ((scanRow = rs.getNextRow()) != null) {
0931: Object[] scanRowArray = scanRow.getRowArray();
0932: int i;
0933: for (i = 0; i < colsToCheck.length; i++) {
0934: checkCol = (DataValueDescriptor) checkRowArray[colsToCheck[i] - 1];
0935: scanCol = (DataValueDescriptor) scanRowArray[colsToCheck[i] - 1];
0936:
0937: BooleanDataValue result = checkCol.equals(scanCol,
0938: checkCol); // result
0939: if (!result.getBoolean()) {
0940: break;
0941: }
0942: }
0943: if (i == colsToCheck.length) {
0944: foundMatch = true;
0945: break;
0946: }
0947: }
0948: } finally {
0949: rs.close();
0950: }
0951: return foundMatch;
0952: }
0953:
0954: /**
0955: * @see ResultSet#cleanUp
0956: *
0957: * @exception StandardException Thrown on error
0958: */
0959: public void cleanUp() throws StandardException {
0960: numOpens = 0;
0961:
0962: /* Close down the source ResultSet tree */
0963: if (source != null) {
0964: source.close();
0965: // cache source across open()s
0966: }
0967:
0968: if (triggerActivator != null) {
0969: triggerActivator.cleanup();
0970: // cache triggerActivator across open()s
0971: }
0972:
0973: if (rowChanger != null)
0974: rowChanger.close();
0975:
0976: if (deferredBaseCC != null)
0977: deferredBaseCC.close();
0978: deferredBaseCC = null;
0979:
0980: if (insertedRowHolder != null) {
0981: insertedRowHolder.close();
0982: }
0983:
0984: if (deletedRowHolder != null) {
0985: deletedRowHolder.close();
0986: }
0987:
0988: if (riChecker != null) {
0989: riChecker.close();
0990: // cache riChecker across open()s
0991: }
0992:
0993: super .close();
0994:
0995: endTime = getCurrentTimeMillis();
0996: }
0997:
0998: /**
0999: * Decode the update lock mode.
1000: * <p>
1001: * The value for update lock mode is in the 2nd 2 bytes for
1002: * ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL isolation level. Otherwise
1003: * (REPEATABLE READ, READ COMMITTED, and READ UNCOMMITTED) the lock mode is
1004: * located in the first 2 bytes.
1005: * <p>
1006: * This is done to override the optimizer choice to provide maximum
1007: * concurrency of record level locking except in SERIALIZABLE where table
1008: * level locking is required in heap scans for correctness.
1009: * <p>
1010: * See Compilation!QueryTree!FromBaseTable for encoding of the lockmode.
1011: * <p>
1012: *
1013: * @return The lock mode (record or table) to use to open the result set.
1014: *
1015: * @param lcc The context to look for current isolation level.
1016: * @param lockMode The compiled encoded lock mode for this query.
1017: *
1018: * @exception StandardException Standard exception policy.
1019: **/
1020: protected static int decodeLockMode(LanguageConnectionContext lcc,
1021: int lockMode) {
1022: if ((lockMode >>> 16) != 0) {
1023: // Note that isolation level encoding from
1024: // getCurrentIsolationLevel() returns
1025: // ExecutionContext.*ISOLATION_LEVEL constants, not
1026: // TransactionController.ISOLATION* constants.
1027:
1028: int isolationLevel = lcc.getCurrentIsolationLevel();
1029:
1030: if (isolationLevel != ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL) {
1031: lockMode = lockMode & 0xff;
1032: } else {
1033: lockMode = lockMode >>> 16;
1034: }
1035: }
1036: return lockMode;
1037: }
1038:
1039: void rowChangerFinish() throws StandardException {
1040: rowChanger.finish();
1041: }
1042:
1043: }
|