0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.store.raw.data.BasePage
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.store.raw.data;
0023:
0024: import org.apache.derby.iapi.reference.SQLState;
0025:
0026: import org.apache.derby.iapi.services.io.FormatableBitSet;
0027: import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream;
0028: import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream;
0029:
0030: import org.apache.derby.iapi.services.locks.C_LockFactory;
0031: import org.apache.derby.iapi.services.locks.Lockable;
0032: import org.apache.derby.iapi.services.locks.Latch;
0033: import org.apache.derby.iapi.services.locks.VirtualLockTable;
0034:
0035: import org.apache.derby.iapi.services.sanity.SanityManager;
0036:
0037: import org.apache.derby.iapi.services.io.LimitObjectInput;
0038: import org.apache.derby.iapi.services.io.TypedFormat;
0039:
0040: import org.apache.derby.iapi.error.StandardException;
0041:
0042: import org.apache.derby.iapi.store.raw.AuxObject;
0043: import org.apache.derby.iapi.store.raw.ContainerHandle;
0044: import org.apache.derby.iapi.store.raw.ContainerKey;
0045: import org.apache.derby.iapi.store.raw.FetchDescriptor;
0046: import org.apache.derby.iapi.store.raw.Page;
0047: import org.apache.derby.iapi.store.raw.PageKey;
0048: import org.apache.derby.iapi.store.raw.RecordHandle;
0049: import org.apache.derby.iapi.store.raw.RawStoreFactory;
0050: import org.apache.derby.iapi.store.raw.xact.RawTransaction;
0051: import org.apache.derby.iapi.store.raw.log.LogInstant;
0052:
0053: import org.apache.derby.iapi.store.access.Qualifier;
0054: import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo;
0055:
0056: import org.apache.derby.iapi.types.DataValueDescriptor;
0057:
0058: import java.io.IOException;
0059: import java.io.OutputStream;
0060: import java.io.ObjectInput;
0061:
0062: import java.util.Hashtable;
0063: import java.util.Observer;
0064: import java.util.Observable;
0065:
0066: /**
0067:
0068: This class implements all the the generic locking behaviour for a Page.
0069: It leaves method used to log and store the records up to sub-classes.
0070: It is intended that the object can represent multiple pages from different
0071: containers during its lifetime.
0072: <P>
0073: A page contains a set of records, which can be accessed by "slot",
0074: which defines the order of the records on the page, or by "id" which
0075: defines the identity of the records on the page. Clients access
0076: records by both slot and id, depending on their needs.
0077: <P>
0078: BasePage implements Observer to watch the ContainerHandle which notifies
0079: its Observers when it is closing.
0080:
0081: <BR>
0082: MT - mutable
0083:
0084: **/
0085:
0086: public abstract class BasePage implements Page, Lockable, Observer,
0087: TypedFormat {
0088:
0089: /**
0090: auxiliary object
0091:
0092: MT - mutable - content dynamic : single thread required. This reference is
0093: set while the page is latched and returned to callers of page while the page is latched.
0094: For correct MT behaviour it is assumed that the caller discards any reference to an
0095: auxiliary object once the page is unlatched. The reference mya be cleared while
0096: the page is latched, or while the page is being cleaned from the cache. In the latter
0097: case the cache manager ensures that only a single thread can access this object.
0098: */
0099: private AuxObject auxObj;
0100:
0101: /**
0102: this page's identity
0103: <BR>
0104: MT - immutable - content dynamic : single thread required
0105: */
0106: protected PageKey identity;
0107:
0108: /**
0109: In-memory slot table, array of StoredRecordHeaders.
0110: <BR>
0111: MT - Immutable - Content Dynamic : Single thread required.
0112: */
0113: private StoredRecordHeader[] headers; // in memory slot table
0114:
0115: private int recordCount;
0116:
0117: /**
0118: Page owner during exclusive access.
0119:
0120: MT - mutable : single thread required, provided by Lockable single thread required.
0121: */
0122: protected BaseContainerHandle owner;
0123:
0124: /**
0125: Count of times a latch is held nested during an abort
0126: */
0127: private int nestedLatch;
0128:
0129: /**
0130: LockManager held latch during exclusive access.
0131: When this is not null, latch.getQualifier() == owner
0132: */
0133: private Latch myLatch;
0134:
0135: protected boolean inClean; // is the page being cleaned
0136:
0137: /**
0138: * Used to determine latch state of a page.
0139: *
0140: * MT - mutable
0141: *
0142: * There are 3 latch states for a page:
0143: *
0144: * UNLATCHED - (owner == null)
0145: * PRELATCH - (owner != null) && preLatch
0146: * LATCHED - (owner != null) && !preLatch
0147: *
0148: * A page may be "cleaned" while it is either UNLATCHED, or PRELATCH, but
0149: * it must wait for it to be not LATCHED.
0150: *
0151: * A page may move from UNLATCHED to PRELATCH, while being cleaned.
0152: * A page must wait for !inClean before it can move from PRELATCH to
0153: * LATCHED.
0154: **/
0155: protected boolean preLatch;
0156:
0157: /**
0158: Instant of last log record that updated this page.
0159:
0160: <BR> MT - mutable : latched
0161: */
0162: private LogInstant lastLog;
0163:
0164: /**
0165: Version of the page.
0166:
0167: <BR> MT - mutable : single thread required - The page must be latched to access
0168: this variable or the page muts be in the noidentiy state.
0169: */
0170: private long pageVersion = 0; // version of the page
0171:
0172: /**
0173: Status of the page
0174: */
0175: private byte pageStatus;
0176:
0177: /**
0178: Values for pageStatus flag
0179:
0180: page goes thru the following transition:
0181: VALID_PAGE <-> deallocated page -> free page <-> VALID_PAGE
0182:
0183: deallocated and free page are both INVALID_PAGE as far as BasePage is concerned.
0184: When a page is deallocated, it transitioned from VALID to INVALID.
0185: When a page is allocated, it trnasitioned from INVALID to VALID.
0186:
0187: */
0188: public static final byte VALID_PAGE = 1;
0189: public static final byte INVALID_PAGE = 2;
0190:
0191: /**
0192: Init page flag.
0193:
0194: INIT_PAGE_REUSE - set if page is being initialized for reuse
0195: INIT_PAGE_OVERFLOW - set if page will be an overflow page
0196: INIT_PAGE_REUSE_RECORDID - set if page is being reused and its record
0197: id can be reset to RecordHandle.FIRST_RECORD_ID, rather
0198: to 1+ next recordId on the page
0199: */
0200: public static final int INIT_PAGE_REUSE = 0x1;
0201: public static final int INIT_PAGE_OVERFLOW = 0x2;
0202: public static final int INIT_PAGE_REUSE_RECORDID = 0x4;
0203:
0204: /**
0205: Log Record flag. Why the before image of this record is being logged
0206:
0207: LOG_RECORD_FOR_UPDATE - set if the record is being logged for update.
0208: LOG_RECORD_DEFAULT - for non update.
0209: LOG_RECORD_FOR_PURGE - set if the record is being logged for purges
0210: and no data required to ve logged.
0211: The other cases (copy, purge, delete), we don't need to distinguish,
0212: leave no bit set.
0213: */
0214: public static final int LOG_RECORD_DEFAULT = 0x0;
0215: public static final int LOG_RECORD_FOR_UPDATE = 0x1;
0216: public static final int LOG_RECORD_FOR_PURGE = 0x2;
0217:
0218: /**
0219: ** Create a new, empty page.
0220: **/
0221:
0222: protected BasePage() {
0223:
0224: }
0225:
0226: /**
0227: Initialized the BasePage.
0228: <p>
0229: Initialize the object, ie. perform work normally perfomed in
0230: constructor. Called by setIdentity() and createIdentity().
0231: */
0232: protected void initialize() {
0233: setAuxObject(null);
0234: identity = null;
0235: recordCount = 0;
0236: clearLastLogInstant();
0237:
0238: if (SanityManager.DEBUG) {
0239: if (nestedLatch != 0)
0240: SanityManager
0241: .THROWASSERT("nestedLatch is non-zero in initialize - value = "
0242: + nestedLatch);
0243: if (inClean)
0244: SanityManager
0245: .THROWASSERT("inClean is true in initialize");
0246: if (preLatch)
0247: SanityManager
0248: .THROWASSERT("preLatch is true in initialize");
0249: }
0250:
0251: }
0252:
0253: /**
0254: Must be called by a sub-class before calling setHeaderAtSlot.
0255:
0256: */
0257: protected void initializeHeaders(int numRecords) {
0258:
0259: if (SanityManager.DEBUG) {
0260: if (recordCount != 0)
0261: SanityManager.THROWASSERT("record count = "
0262: + recordCount
0263: + " before initSlotTable is called");
0264: }
0265:
0266: headers = new StoredRecordHeader[numRecords];
0267: }
0268:
0269: /*
0270: ** Cacheable methods
0271: */
0272:
0273: protected void fillInIdentity(PageKey key) {
0274: if (SanityManager.DEBUG) {
0275: SanityManager.ASSERT(identity == null);
0276: }
0277:
0278: identity = key;
0279: }
0280:
0281: public void clearIdentity() {
0282:
0283: if (SanityManager.DEBUG) {
0284: SanityManager.ASSERT(!isLatched());
0285: }
0286:
0287: identity = null;
0288:
0289: cleanPageForReuse();
0290: }
0291:
0292: /**
0293: Initialized this page for reuse or first use
0294: */
0295: protected void cleanPageForReuse() {
0296: setAuxObject(null);
0297: recordCount = 0;
0298: }
0299:
0300: /**
0301: OK to hand object outside to cache..
0302: */
0303: public Object getIdentity() {
0304: return identity;
0305: }
0306:
0307: /*
0308: ** Methods of Page
0309: */
0310:
0311: private static final RecordHandle InvalidRecordHandle = new RecordId(
0312: new PageKey(new ContainerKey(0, 0),
0313: ContainerHandle.INVALID_PAGE_NUMBER),
0314: RecordHandle.INVALID_RECORD_HANDLE);
0315:
0316: public final RecordHandle getInvalidRecordHandle() {
0317: // a static invalid record handle
0318: return InvalidRecordHandle;
0319: }
0320:
0321: public static final RecordHandle MakeRecordHandle(PageKey pkey,
0322: int recordHandleConstant) throws StandardException {
0323: if (recordHandleConstant >= RecordHandle.FIRST_RECORD_ID) {
0324: throw StandardException.newException(
0325: SQLState.DATA_CANNOT_MAKE_RECORD_HANDLE, new Long(
0326: recordHandleConstant));
0327: }
0328:
0329: return new RecordId(pkey, recordHandleConstant);
0330: }
0331:
0332: public final RecordHandle makeRecordHandle(int recordHandleConstant)
0333: throws StandardException {
0334: return MakeRecordHandle(getPageId(), recordHandleConstant);
0335: }
0336:
0337: /** @see Page#getPageNumber */
0338: public final long getPageNumber() {
0339: if (SanityManager.DEBUG) {
0340: SanityManager.ASSERT(isLatched(), "page is not latched.");
0341: SanityManager.ASSERT(identity != null, "identity is null.");
0342: }
0343:
0344: return identity.getPageNumber();
0345: }
0346:
0347: public final RecordHandle getRecordHandle(int recordId) {
0348: if (SanityManager.DEBUG) {
0349: SanityManager.ASSERT(isLatched());
0350: }
0351:
0352: int slot = findRecordById(recordId, FIRST_SLOT_NUMBER);
0353: if (slot < 0)
0354: return null;
0355:
0356: return getRecordHandleAtSlot(slot);
0357: }
0358:
0359: public final RecordHandle getRecordHandleAtSlot(int slot) {
0360: return getHeaderAtSlot(slot).getHandle(getPageId(), slot);
0361: }
0362:
0363: /**
0364: @see Page#recordExists
0365: @exception StandardException recordHandle is not a valid record handle
0366: */
0367: public final boolean recordExists(RecordHandle handle,
0368: boolean ignoreDelete) throws StandardException {
0369: if (SanityManager.DEBUG) {
0370: SanityManager.ASSERT(isLatched());
0371: }
0372:
0373: if (handle.getId() < RecordHandle.FIRST_RECORD_ID) {
0374: throw StandardException.newException(
0375: SQLState.DATA_INVALID_RECORD_HANDLE, handle);
0376: }
0377:
0378: if (handle.getPageNumber() != getPageNumber())
0379: return false;
0380:
0381: int slot = findRecordById(handle.getId(), handle
0382: .getSlotNumberHint());
0383: return (slot >= FIRST_SLOT_NUMBER && (ignoreDelete || !isDeletedAtSlot(slot)));
0384: }
0385:
0386: /**
0387: <OL>
0388: <LI>Lock the record (according to the locking policy)
0389: <LI>If the record is deleted then return null. We must check after we hold the lock to
0390: ensure that we don't look at the delete status of an uncommitted record.
0391: <LI>Fetch the record
0392: <LI>Unlock the record (according to the locking policy)
0393: </OL>
0394:
0395: @see Page#fetch
0396:
0397: @exception StandardException messageId equals StandardException.newException(SQLState.RECORD_VANISHED
0398: If the record identfied by handle does not exist on this page.
0399:
0400: @exception StandardException Standard Cloudscape error policy
0401: @exception StandardException record is not on page with message id equal to
0402: StandardException.newException(SQLState.RECORD_VANISHED.
0403: */
0404:
0405: public RecordHandle fetch(RecordHandle handle, Object[] row,
0406: FormatableBitSet validColumns, boolean forUpdate)
0407: throws StandardException {
0408:
0409: if (SanityManager.DEBUG) {
0410: SanityManager.ASSERT(isLatched());
0411: }
0412:
0413: owner.getLockingPolicy().lockRecordForRead(myLatch, handle,
0414: forUpdate);
0415:
0416: // See if the record is deleted or not.
0417: int slot = getSlotNumber(handle);
0418:
0419: StoredRecordHeader recordHeader = getHeaderAtSlot(slot);
0420:
0421: if (recordHeader.isDeleted())
0422: return null;
0423:
0424: FetchDescriptor hack_fetch = new FetchDescriptor(row.length,
0425: validColumns, (Qualifier[][]) null);
0426:
0427: // magic to copy rows across ...
0428: restoreRecordFromSlot(slot, row, hack_fetch, handle,
0429: recordHeader, true);
0430:
0431: owner.getLockingPolicy().unlockRecordAfterRead(
0432: owner.getTransaction(), owner, handle, forUpdate, true);
0433:
0434: return handle;
0435: }
0436:
0437: public RecordHandle fetchFromSlot(RecordHandle rh, int slot,
0438: Object[] row, FetchDescriptor fetchDesc,
0439: boolean ignoreDelete) throws StandardException {
0440: if (SanityManager.DEBUG) {
0441: SanityManager.ASSERT(isLatched());
0442:
0443: if (rh != null)
0444: SanityManager.ASSERT(getSlotNumber(rh) == slot);
0445: }
0446:
0447: checkSlotOnPage(slot);
0448:
0449: StoredRecordHeader recordHeader = getHeaderAtSlot(slot);
0450:
0451: if (rh == null)
0452: rh = recordHeader.getHandle(getPageId(), slot);
0453:
0454: if (!ignoreDelete && recordHeader.isDeleted())
0455: return null;
0456:
0457: /*
0458: SanityManager.DEBUG_PRINT("fetchFromSlot", "before.");
0459: SanityManager.showTrace(new Throwable());
0460: SanityManager.DEBUG_PRINT("fetchFromSlot", "fetchDesc = " + fetchDesc);
0461:
0462: if (fetchDesc != null)
0463: {
0464: SanityManager.DEBUG_PRINT("fetchFromSlot",
0465: ";fetchDesc.getMaxFetchColumnId() = " +
0466: fetchDesc.getMaxFetchColumnId() +
0467: ";fetchDesc.getValidColumns() = " +
0468: fetchDesc.getValidColumns() +
0469: ";fetchDesc.getQualifierList() = " +
0470: fetchDesc.getQualifierList()
0471: );
0472: }
0473: */
0474:
0475: return (restoreRecordFromSlot(slot, row, fetchDesc, rh,
0476: recordHeader, true) ? rh : null);
0477: }
0478:
0479: /**
0480: @exception StandardException Standard Cloudscape error policy
0481: @see Page#fetchFieldFromSlot
0482: */
0483: public final RecordHandle fetchFieldFromSlot(int slot, int fieldId,
0484: Object column) throws StandardException {
0485: // need to allocate row with fieldId cols because of sparse row change
0486: // needs to be RESOLVED
0487: Object[] row = new Object[fieldId + 1];
0488: row[fieldId] = column;
0489: FormatableBitSet singleColumn = new FormatableBitSet(
0490: fieldId + 1);
0491:
0492: singleColumn.set(fieldId);
0493:
0494: FetchDescriptor fetchDesc = new FetchDescriptor(fieldId + 1,
0495: singleColumn, (Qualifier[][]) null);
0496:
0497: return (fetchFromSlot(null, slot, row, fetchDesc, true));
0498: }
0499:
0500: /**
0501: @exception StandardException Record does not exist on this page.
0502:
0503: @see Page#getSlotNumber
0504: */
0505: public final int getSlotNumber(RecordHandle handle)
0506: throws StandardException {
0507: if (SanityManager.DEBUG) {
0508: SanityManager.ASSERT(isLatched());
0509: }
0510:
0511: int slot = findRecordById(handle.getId(), handle
0512: .getSlotNumberHint());
0513:
0514: if (slot < 0) {
0515: throw StandardException.newException(
0516: SQLState.RAWSTORE_RECORD_VANISHED, handle);
0517: }
0518:
0519: return slot;
0520: }
0521:
0522: /**
0523: @exception StandardException Record does not exist on this page.
0524:
0525: @see Page#getNextSlotNumber
0526: */
0527: public final int getNextSlotNumber(RecordHandle handle)
0528: throws StandardException {
0529: if (SanityManager.DEBUG) {
0530: SanityManager.ASSERT(isLatched());
0531: }
0532:
0533: int slot = findNextRecordById(handle.getId());
0534:
0535: return slot;
0536: }
0537:
0538: /** @see Page#insertAtSlot
0539: @exception StandardException Standard Cloudscape error policy
0540: */
0541: public RecordHandle insertAtSlot(int slot, Object[] row,
0542: FormatableBitSet validColumns, LogicalUndo undo,
0543: byte insertFlag, int overflowThreshold)
0544: throws StandardException {
0545: if (SanityManager.DEBUG) {
0546: if (overflowThreshold == 0)
0547: SanityManager
0548: .THROWASSERT("overflowThreshold cannot be 0");
0549: }
0550:
0551: if ((insertFlag & Page.INSERT_DEFAULT) == Page.INSERT_DEFAULT) {
0552: return (insertNoOverflow(slot, row, validColumns, undo,
0553: insertFlag, overflowThreshold));
0554: } else {
0555: if (SanityManager.DEBUG) {
0556: if (undo != null)
0557: SanityManager
0558: .THROWASSERT("logical undo with overflow allowed on insert "
0559: + undo.toString());
0560: }
0561: return (insertAllowOverflow(slot, row, validColumns, 0,
0562: insertFlag, overflowThreshold, (RecordHandle) null));
0563: }
0564: }
0565:
0566: protected RecordHandle insertNoOverflow(int slot, Object[] row,
0567: FormatableBitSet validColumns, LogicalUndo undo,
0568: byte insertFlag, int overflowThreshold)
0569: throws StandardException {
0570:
0571: if (SanityManager.DEBUG) {
0572: SanityManager.ASSERT(isLatched());
0573: }
0574:
0575: if (!owner.updateOK()) {
0576: throw StandardException
0577: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
0578: }
0579:
0580: if (slot < FIRST_SLOT_NUMBER || slot > recordCount) {
0581: throw StandardException
0582: .newException(SQLState.DATA_SLOT_NOT_ON_PAGE);
0583: }
0584:
0585: if (!allowInsert())
0586: return null;
0587:
0588: RawTransaction t = owner.getTransaction();
0589:
0590: // logical operations not allowed in internal transactions.
0591: if (undo != null) {
0592: t.checkLogicalOperationOk();
0593: }
0594:
0595: int recordId;
0596: RecordHandle handle;
0597:
0598: do {
0599:
0600: // loop until we get a new record id we can get a lock on.
0601:
0602: // If we can't get the lock without waiting then assume the record
0603: // id is owned by another xact. The current heap overflow
0604: // algorithm makes this likely, as it first try's to insert a row
0605: // telling raw store to fail if it doesn't fit on the page getting
0606: // a lock on an id that never makes it to disk. The inserting
0607: // transaction will hold a lock on this "unused" record id until
0608: // it commits. The page can leave the cache at this point, and
0609: // the inserting transaction has not dirtied the page (it failed
0610: // after getting the lock but before logging anything), another
0611: // inserting transaction will then get the same id as the
0612: // previous inserter - thus the loop on lock waits.
0613: //
0614: // The lock we request indicates that this is a lock for insert,
0615: // which the locking policy may use to perform locking concurrency
0616: // optimizations.
0617:
0618: recordId = newRecordIdAndBump();
0619: handle = new RecordId(getPageId(), recordId, slot);
0620:
0621: } while (!owner
0622: .getLockingPolicy()
0623: .lockRecordForWrite(t, handle,
0624: true /* lock is for insert */, false /* don't wait for grant */));
0625:
0626: owner.getActionSet().actionInsert(t, this , slot, recordId, row,
0627: validColumns, undo, insertFlag, 0, false, -1,
0628: (DynamicByteArrayOutputStream) null, -1,
0629: overflowThreshold);
0630:
0631: // at this point the insert has been logged and made on the physical
0632: // page the in-memory manipulation of the slot table is also performed
0633: // by the PageActions object that implements actionInsert.
0634:
0635: return handle;
0636: }
0637:
0638: /** @see Page#insert
0639: @exception StandardException Standard Cloudscape error policy
0640: */
0641: public final RecordHandle insert(Object[] row,
0642: FormatableBitSet validColumns, byte insertFlag,
0643: int overflowThreshold) throws StandardException {
0644:
0645: if (SanityManager.DEBUG) {
0646: if (overflowThreshold == 0)
0647: SanityManager
0648: .THROWASSERT("overflowThreshold much be greater than 0");
0649: }
0650:
0651: if (((insertFlag & Page.INSERT_DEFAULT) == Page.INSERT_DEFAULT)) {
0652: return (insertAtSlot(recordCount, row, validColumns,
0653: (LogicalUndo) null, insertFlag, overflowThreshold));
0654: } else {
0655: return (insertAllowOverflow(recordCount, row, validColumns,
0656: 0, insertFlag, overflowThreshold,
0657: (RecordHandle) null));
0658: }
0659: }
0660:
0661: /**
0662: Insert a row allowing overflow.
0663:
0664: If handle is supplied then the record at that hanlde will be updated
0665: to indicate it is a partial row and it has an overflow portion.
0666:
0667: @exception StandardException Standard Cloudscape error policy
0668: */
0669: public RecordHandle insertAllowOverflow(int slot, Object[] row,
0670: FormatableBitSet validColumns, int startColumn,
0671: byte insertFlag, int overflowThreshold,
0672: RecordHandle nextPortionHandle) throws StandardException {
0673:
0674: BasePage curPage = this ;
0675:
0676: if (!curPage.owner.updateOK()) {
0677: throw StandardException
0678: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
0679: }
0680:
0681: // Handle of the first portion of the chain
0682: RecordHandle headHandle = null;
0683: RecordHandle handleToUpdate = null;
0684:
0685: RawTransaction t = curPage.owner.getTransaction();
0686:
0687: for (;;) {
0688:
0689: if (SanityManager.DEBUG) {
0690: SanityManager.ASSERT(curPage.isLatched());
0691: }
0692:
0693: if (!curPage.allowInsert())
0694: return null;
0695:
0696: // 'this' is the head page
0697: if (curPage != this )
0698: slot = curPage.recordCount;
0699:
0700: boolean isLongColumns = false;
0701: int realStartColumn = -1;
0702: int realSpaceOnPage = -1;
0703:
0704: DynamicByteArrayOutputStream logBuffer = null;
0705:
0706: // allocate new record id and handle
0707: int recordId = curPage.newRecordIdAndBump();
0708: RecordHandle handle = new RecordId(curPage.getPageId(),
0709: recordId, slot);
0710:
0711: if (curPage == this ) {
0712:
0713: // Lock the row, if it is the very first portion of the record.
0714: if (handleToUpdate == null) {
0715:
0716: while (!owner.getLockingPolicy()
0717: .lockRecordForWrite(t, handle,
0718: true /* lock is for insert */,
0719: false /* don't wait for grant */)) {
0720:
0721: // loop until we get a new record id we can get a lock
0722: // on. If we can't get the lock without waiting then
0723: // assume the record id is owned by another xact. The
0724: // current heap overflow algorithm makes this likely,
0725: // as it first try's to insert a row telling raw store
0726: // to fail if it doesn't fit on the page getting a lock
0727: // on an id that never makes it to disk. The
0728: // inserting transaction will hold a lock on this
0729: // "unused" record id until it commits. The page can
0730: // leave the cache at this point, and the inserting
0731: // transaction has not dirtied the page (it failed
0732: // after getting the lock but before logging anything),
0733: // another inserting transaction will then get the
0734: // same id as the previous inserter - thus the loop on
0735: // lock waits.
0736: //
0737: // The lock we request indicates that this is a lock
0738: // for insert, which the locking policy may use to
0739: // perform locking concurrency optimizations.
0740:
0741: // allocate new record id and handle
0742: recordId = curPage.newRecordIdAndBump();
0743: handle = new RecordId(curPage.getPageId(),
0744: recordId, slot);
0745: }
0746: }
0747:
0748: headHandle = handle;
0749: }
0750:
0751: do {
0752:
0753: // do this loop at least once. If we caught a long Column,
0754: // then, we redo the insert with saved logBuffer.
0755: try {
0756:
0757: startColumn = owner.getActionSet().actionInsert(t,
0758: curPage, slot, recordId, row, validColumns,
0759: (LogicalUndo) null, insertFlag,
0760: startColumn, false, realStartColumn,
0761: logBuffer, realSpaceOnPage,
0762: overflowThreshold);
0763: isLongColumns = false;
0764:
0765: } catch (LongColumnException lce) {
0766:
0767: // we caught a long column exception
0768: // three things should happen here:
0769: // 1. insert the long column into overflow pages.
0770: // 2. append the overflow field header in the main chain.
0771: // 3. continue the insert in the main data chain.
0772: logBuffer = new DynamicByteArrayOutputStream(lce
0773: .getLogBuffer());
0774:
0775: // step 1: insert the long column ... use the same
0776: // insertFlag as the rest of the row.
0777: RecordHandle longColumnHandle = insertLongColumn(
0778: curPage, lce, insertFlag);
0779:
0780: // step 2: append the overflow field header to the log buffer
0781: int overflowFieldLen = 0;
0782: try {
0783: overflowFieldLen += appendOverflowFieldHeader(
0784: (DynamicByteArrayOutputStream) logBuffer,
0785: longColumnHandle);
0786: } catch (IOException ioe) {
0787: // YYZ: revisit... ioexception, insert failed...
0788: return null;
0789: }
0790:
0791: // step 3: continue the insert in the main data chain
0792: // need to pass the log buffer, and start column to the next insert.
0793: realStartColumn = lce.getNextColumn() + 1;
0794: realSpaceOnPage = lce.getRealSpaceOnPage()
0795: - overflowFieldLen;
0796:
0797: isLongColumns = true;
0798: }
0799: } while (isLongColumns);
0800:
0801: if (handleToUpdate != null) {
0802: // update the recordheader on the previous page
0803: updateOverflowDetails(handleToUpdate, handle);
0804: }
0805:
0806: // all done
0807: if (startColumn == -1) {
0808:
0809: if (curPage != this )
0810: curPage.unlatch();
0811:
0812: if (nextPortionHandle != null) {
0813: // need to update the overflow details of the last portion
0814: // to point to the existing portion
0815: updateOverflowDetails(handle, nextPortionHandle);
0816: }
0817:
0818: return headHandle;
0819: }
0820:
0821: handleToUpdate = handle;
0822:
0823: BasePage nextPage = curPage.getOverflowPageForInsert(slot,
0824: row, validColumns, startColumn);
0825:
0826: if (curPage != this )
0827: curPage.unlatch();
0828: curPage = nextPage;
0829: }
0830:
0831: }
0832:
0833: /**
0834:
0835: When we update a column, it turned into a long column. Need to change
0836: the update to effectively insert a new long column chain.
0837:
0838: @exception StandardException Unexpected exception from the implementation
0839: */
0840: protected RecordHandle insertLongColumn(BasePage mainChainPage,
0841: LongColumnException lce, byte insertFlag)
0842: throws StandardException {
0843:
0844: // Object[] row = new Object[1];
0845: // row[0] = (Object) lce.getColumn();
0846: Object[] row = new Object[1];
0847: row[0] = lce.getColumn();
0848:
0849: RecordHandle firstHandle = null;
0850: RecordHandle handle = null;
0851: RecordHandle prevHandle = null;
0852: BasePage curPage = mainChainPage;
0853: BasePage prevPage = null;
0854: boolean isFirstPage = true;
0855:
0856: // when inserting a long column startCOlumn is just used
0857: // as a flag. -1 means the insert is complete, != -1 indicates
0858: // more inserts are required.
0859: int startColumn = 0;
0860: RawTransaction t = curPage.owner.getTransaction();
0861:
0862: do {
0863: // in this loop, we do 3 things:
0864: // 1. get a new overflow page
0865: // 2. insert portion of a long column
0866: // 3. update previous handle, release latch on previous page
0867:
0868: if (!isFirstPage) {
0869: prevPage = curPage;
0870: prevHandle = handle;
0871: }
0872:
0873: // step 1. get a new overflow page
0874: curPage = (BasePage) getNewOverflowPage();
0875:
0876: if (SanityManager.DEBUG) {
0877: SanityManager.ASSERT(curPage.isLatched());
0878: SanityManager.ASSERT(curPage.allowInsert());
0879: }
0880:
0881: int slot = curPage.recordCount;
0882:
0883: int recordId = curPage.newRecordId();
0884: handle = new RecordId(curPage.getPageId(), recordId, slot);
0885:
0886: if (isFirstPage)
0887: firstHandle = handle;
0888:
0889: // step 2: insert column portion
0890: startColumn = owner.getActionSet().actionInsert(t, curPage,
0891: slot, recordId, row, (FormatableBitSet) null,
0892: (LogicalUndo) null, insertFlag, startColumn, true,
0893: -1, (DynamicByteArrayOutputStream) null, -1, 100);
0894:
0895: // step 3: if it is not the first page, update previous page,
0896: // then release latch on prevPage
0897: if (!isFirstPage) {
0898: // for the previous page, add an overflow field header,
0899: // and update the record header to show 2 fields
0900: prevPage.updateFieldOverflowDetails(prevHandle, handle);
0901: prevPage.unlatch();
0902: prevPage = null;
0903: } else
0904: isFirstPage = false;
0905:
0906: } while (startColumn != (-1));
0907:
0908: if (curPage != null) {
0909: curPage.unlatch();
0910: curPage = null;
0911: }
0912:
0913: return (firstHandle);
0914: }
0915:
0916: /**
0917: The page or its header is about to be modified.
0918: Loggable actions use this to make sure the page gets cleaned if a
0919: checkpoint is taken after any log record is sent to the log stream but
0920: before the page is actually dirtied.
0921: */
0922: public abstract void preDirty();
0923:
0924: /**
0925: Update the overflow pointer for a long row
0926:
0927: <BR> MT - latched - page latch must be held
0928:
0929: @param handle handle of the record for long row
0930: @param overflowHandle the overflow (continuation) pointer for the long row
0931:
0932: @exception StandardException Standard Cloudscape error policy
0933: */
0934: public abstract void updateOverflowDetails(RecordHandle handle,
0935: RecordHandle overflowHandle) throws StandardException;
0936:
0937: /**
0938: Update the overflow pointer for a long column
0939:
0940: <BR> MT - latched - page latch must be held
0941:
0942: @param handle handle of the record for long row
0943: @param overflowHandle the overflow (continuation) pointer for the long row
0944:
0945: @exception StandardException Standard Cloudscape error policy
0946: */
0947: public abstract void updateFieldOverflowDetails(
0948: RecordHandle handle, RecordHandle overflowHandle)
0949: throws StandardException;
0950:
0951: /**
0952: Append an overflow pointer to a partly logged row,
0953: to point to a long column that just been logged.
0954:
0955: <BR> MT - latched - page latch must be held
0956:
0957: @param logBuffer The buffer that contains the partially logged row.
0958: @param overflowHandle the overflow (continuation) pointer
0959: to the beginning of the long column
0960:
0961: @exception StandardException Standard Cloudscape error policy
0962: */
0963: public abstract int appendOverflowFieldHeader(
0964: DynamicByteArrayOutputStream logBuffer,
0965: RecordHandle overflowHandle) throws StandardException,
0966: IOException;
0967:
0968: public abstract BasePage getOverflowPageForInsert(int slot,
0969: Object[] row, FormatableBitSet validColumns, int startColumn)
0970: throws StandardException;
0971:
0972: protected abstract BasePage getNewOverflowPage()
0973: throws StandardException;
0974:
0975: public final boolean update(RecordHandle handle, Object[] row,
0976: FormatableBitSet validColumns) throws StandardException {
0977: if (SanityManager.DEBUG) {
0978: SanityManager.ASSERT(isLatched());
0979: }
0980:
0981: if (!owner.updateOK()) {
0982: throw StandardException
0983: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
0984: }
0985:
0986: RawTransaction t = owner.getTransaction();
0987:
0988: owner.getLockingPolicy().lockRecordForWrite(myLatch, handle);
0989:
0990: int slot = getSlotNumber(handle);
0991:
0992: if (isDeletedAtSlot(slot))
0993: return false;
0994:
0995: doUpdateAtSlot(t, slot, handle.getId(), row, validColumns);
0996:
0997: return true;
0998: }
0999:
1000: /** @see Page#delete
1001: @see BasePage#deleteAtSlot
1002: @exception StandardException Standard exception policy.
1003: */
1004: public boolean delete(RecordHandle handle, LogicalUndo undo)
1005: throws StandardException {
1006: if (SanityManager.DEBUG) {
1007: SanityManager.ASSERT(isLatched());
1008: }
1009:
1010: owner.getLockingPolicy().lockRecordForWrite(myLatch, handle);
1011:
1012: int slot = getSlotNumber(handle);
1013:
1014: if (isDeletedAtSlot(slot))
1015: return false;
1016:
1017: deleteAtSlot(slot, true, undo);
1018: return true;
1019: }
1020:
1021: /** @see Page#updateAtSlot
1022: @exception StandardException Standard Cloudscape error policy
1023: @exception StandardException StandardException.newException(SQLState.UPDATE_DELETED_RECORD
1024: if the record is already deleted
1025: @exception StandardException StandardException.newException(SQLState.CONTAINER_READ_ONLY
1026: if the container is read only
1027: */
1028: public final RecordHandle updateAtSlot(int slot, Object[] row,
1029: FormatableBitSet validColumns) throws StandardException {
1030: if (SanityManager.DEBUG) {
1031: SanityManager.ASSERT(isLatched());
1032: }
1033:
1034: if (!owner.updateOK()) {
1035: throw StandardException
1036: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1037: }
1038:
1039: if (isDeletedAtSlot(slot)) {
1040: throw StandardException
1041: .newException(SQLState.DATA_UPDATE_DELETED_RECORD);
1042: }
1043:
1044: RecordHandle handle = getRecordHandleAtSlot(slot);
1045:
1046: RawTransaction t = owner.getTransaction();
1047:
1048: doUpdateAtSlot(t, slot, handle.getId(), row, validColumns);
1049:
1050: return handle;
1051: }
1052:
1053: public abstract void doUpdateAtSlot(RawTransaction t, int slot,
1054: int id, Object[] row, FormatableBitSet validColumns)
1055: throws StandardException;
1056:
1057: /** @see Page#updateFieldAtSlot
1058: @exception StandardException Standard Cloudscape error policy
1059: @exception StandardException StandardException.newException(SQLState.UPDATE_DELETED_RECORD
1060: if the record is already deleted
1061: @exception StandardException StandardException.newException(SQLState.CONTAINER_READ_ONLY
1062: if the container is read only
1063: */
1064: public RecordHandle updateFieldAtSlot(int slot, int fieldId,
1065: Object newValue, LogicalUndo undo) throws StandardException {
1066: if (SanityManager.DEBUG) {
1067: SanityManager.ASSERT(isLatched());
1068: SanityManager.ASSERT(newValue != null);
1069: }
1070:
1071: if (!owner.updateOK()) {
1072: throw StandardException
1073: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1074: }
1075:
1076: if (isDeletedAtSlot(slot)) {
1077: throw StandardException
1078: .newException(SQLState.DATA_UPDATE_DELETED_RECORD);
1079: }
1080:
1081: RawTransaction t = owner.getTransaction();
1082: RecordHandle handle = getRecordHandleAtSlot(slot);
1083:
1084: owner.getActionSet().actionUpdateField(t, this , slot,
1085: handle.getId(), fieldId, newValue, undo);
1086:
1087: return handle;
1088: }
1089:
1090: /** @see Page#fetchNumFields
1091: @exception StandardException Standard exception policy.
1092: */
1093: public final int fetchNumFields(RecordHandle handle)
1094: throws StandardException {
1095: if (SanityManager.DEBUG) {
1096: SanityManager.ASSERT(isLatched());
1097: }
1098:
1099: return fetchNumFieldsAtSlot(getSlotNumber(handle));
1100: }
1101:
1102: /** @see Page#fetchNumFieldsAtSlot
1103: @exception StandardException Standard exception policy.
1104: */
1105: public int fetchNumFieldsAtSlot(int slot) throws StandardException {
1106: if (SanityManager.DEBUG) {
1107: SanityManager.ASSERT(isLatched());
1108: }
1109:
1110: return getHeaderAtSlot(slot).getNumberFields();
1111: }
1112:
1113: /**
1114: @see Page#deleteAtSlot
1115:
1116: @param slot the slot number
1117: @param delete true if this record is to be deleted, false if this
1118: deleted record is to be marked undeleted
1119: @param undo logical undo logic if necessary
1120:
1121: @exception StandardException Standard exception policy.
1122: @exception StandardException StandardException.newException(SQLState.UPDATE_DELETED_RECORD
1123: if an attempt to delete a record that is already deleted
1124: @exception StandardException StandardException.newException(SQLState.UNDELETE_RECORD
1125: if an attempt to undelete a record that is not deleted
1126: */
1127: public RecordHandle deleteAtSlot(int slot, boolean delete,
1128: LogicalUndo undo) throws StandardException {
1129: if (SanityManager.DEBUG) {
1130: SanityManager.ASSERT(isLatched());
1131: }
1132:
1133: if (!owner.updateOK()) {
1134: throw StandardException
1135: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1136: }
1137:
1138: if (delete) {
1139: if (isDeletedAtSlot(slot)) {
1140: throw StandardException
1141: .newException(SQLState.DATA_UPDATE_DELETED_RECORD);
1142: }
1143:
1144: } else // undelete a deleted record
1145: {
1146: if (!isDeletedAtSlot(slot)) {
1147: throw StandardException
1148: .newException(SQLState.DATA_UNDELETE_RECORD);
1149: }
1150: }
1151:
1152: RawTransaction t = owner.getTransaction();
1153:
1154: // logical operations not allowed in internal transactions.
1155: if (undo != null) {
1156: t.checkLogicalOperationOk();
1157: }
1158:
1159: RecordHandle handle = getRecordHandleAtSlot(slot);
1160:
1161: owner.getActionSet().actionDelete(t, this , slot,
1162: handle.getId(), delete, undo);
1163:
1164: // delete/undelete the record in the stored version
1165: // and in the in memory version performed by the PageActions item
1166:
1167: return handle;
1168: }
1169:
1170: /**
1171: Purge one or more rows on a non-overflow page.
1172:
1173: @see Page#purgeAtSlot
1174: @exception StandardException Standard exception policy.
1175: */
1176: public void purgeAtSlot(int slot, int numpurges,
1177: boolean needDataLogged) throws StandardException {
1178: if (SanityManager.DEBUG) {
1179: SanityManager.ASSERT(isLatched());
1180:
1181: if (isOverflowPage())
1182: SanityManager
1183: .THROWASSERT("purge committed deletes on an overflow page. Page = "
1184: + this );
1185: }
1186:
1187: if (numpurges <= 0)
1188: return;
1189:
1190: if (!owner.updateOK()) {
1191: throw StandardException
1192: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1193: }
1194:
1195: if ((slot < 0) || ((slot + numpurges) > recordCount)) {
1196:
1197: throw StandardException
1198: .newException(SQLState.DATA_SLOT_NOT_ON_PAGE);
1199: }
1200:
1201: RawTransaction t = owner.getTransaction();
1202:
1203: // lock the records to be purged
1204: int[] recordIds = new int[numpurges];
1205:
1206: PageKey pageId = getPageId(); // RESOLVE: MT problem ?
1207:
1208: for (int i = 0; i < numpurges; i++) {
1209: recordIds[i] = getHeaderAtSlot(slot + i).getId();
1210:
1211: // get row lock on head row piece
1212: RecordHandle handle = getRecordHandleAtSlot(slot);
1213: owner.getLockingPolicy().lockRecordForWrite(t, handle,
1214: false, true);
1215:
1216: // Before we purge these rows, we need to make sure they don't have
1217: // overflow rows and columns. Only clean up long rows and long
1218: // columns if this is not a temporary container, otherwise, just
1219: // loose the space.
1220:
1221: if (owner.isTemporaryContainer()
1222: || entireRecordOnPage(slot + i))
1223: continue;
1224:
1225: // row[slot+i] has overflow rows and/or long columns, reclaim
1226: // them in a loop.
1227: RecordHandle headRowHandle = getHeaderAtSlot(slot + i)
1228: .getHandle(pageId, slot + i);
1229: purgeRowPieces(t, slot + i, headRowHandle, needDataLogged);
1230: }
1231:
1232: owner.getActionSet().actionPurge(t, this , slot, numpurges,
1233: recordIds, needDataLogged);
1234:
1235: }
1236:
1237: /**
1238: Purge all the overflow columns and overflow rows of the record at slot.
1239: @exception StandardException Standard exception policy.
1240: */
1241: protected abstract void purgeRowPieces(RawTransaction t, int slot,
1242: RecordHandle headRowHandle, boolean needDataLogged)
1243: throws StandardException;
1244:
1245: /** @see Page#copyAndPurge
1246: @exception StandardException Standard exception policy.
1247: */
1248: public void copyAndPurge(Page destPage, int src_slot, int num_rows,
1249: int dest_slot) throws StandardException {
1250: if (SanityManager.DEBUG) {
1251: SanityManager.ASSERT(isLatched());
1252: }
1253:
1254: if (num_rows <= 0) {
1255: throw StandardException
1256: .newException(SQLState.DATA_NO_ROW_COPIED);
1257: }
1258:
1259: if (!owner.updateOK()) {
1260: throw StandardException
1261: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1262: }
1263:
1264: if ((src_slot < 0) || ((src_slot + num_rows) > recordCount)) {
1265: throw StandardException
1266: .newException(SQLState.DATA_SLOT_NOT_ON_PAGE);
1267: }
1268:
1269: if (SanityManager.DEBUG) {
1270: // first copy into the destination page, let it do the work
1271: // if no problem, then purge from this page
1272: SanityManager.ASSERT((destPage instanceof BasePage),
1273: "must copy from BasePage to BasePage");
1274: }
1275:
1276: BasePage dpage = (BasePage) destPage;
1277:
1278: // make sure they are from the same container - this means they are of
1279: // the same size and have the same page and record format.
1280:
1281: PageKey pageId = getPageId(); // RESOLVE: MT problem ?
1282:
1283: if (!pageId.getContainerId().equals(
1284: dpage.getPageId().getContainerId())) {
1285: throw StandardException.newException(
1286: SQLState.DATA_DIFFERENT_CONTAINER, pageId
1287: .getContainerId(), dpage.getPageId()
1288: .getContainerId());
1289: }
1290:
1291: int[] recordIds = new int[num_rows];
1292:
1293: RawTransaction t = owner.getTransaction();
1294:
1295: // lock the records to be purged and calculate total space needed
1296: for (int i = 0; i < num_rows; i++) {
1297: RecordHandle handle = getRecordHandleAtSlot(src_slot + i);
1298: owner.getLockingPolicy().lockRecordForWrite(t, handle,
1299: false, true);
1300:
1301: recordIds[i] = getHeaderAtSlot(src_slot + i).getId();
1302: }
1303:
1304: // first copy num_rows into destination page
1305: dpage.copyInto(this , src_slot, num_rows, dest_slot);
1306:
1307: // Now purge num_rows from this page
1308: // Do NOT purge overflow rows, if it has such a thing. This operation
1309: // is called by split and if the key has overflow, spliting the head
1310: // page does not copy over the remaining pieces, i.e.,the new head page
1311: // still points to those pieces.
1312:
1313: owner.getActionSet().actionPurge(t, this , src_slot, num_rows,
1314: recordIds, true);
1315: }
1316:
1317: /**
1318: Unlatch the page.
1319: @see Page#unlatch
1320: */
1321: public void unlatch() {
1322: if (SanityManager.DEBUG) {
1323: SanityManager.ASSERT(isLatched());
1324: }
1325:
1326: releaseExclusive();
1327: }
1328:
1329: /** @see Page#isLatched */
1330: public boolean isLatched() {
1331: if (SanityManager.DEBUG) {
1332:
1333: synchronized (this ) {
1334: SanityManager.ASSERT(identity != null);
1335: if (owner != null) {
1336: if (owner != myLatch.getQualifier())
1337: SanityManager
1338: .THROWASSERT("Page incorrectly latched - "
1339: + owner
1340: + " "
1341: + myLatch.getQualifier());
1342: }
1343: }
1344: }
1345:
1346: return owner != null;
1347: }
1348:
1349: /** @see Page#recordCount */
1350: public final int recordCount() {
1351: if (SanityManager.DEBUG) {
1352: SanityManager.ASSERT(isLatched());
1353: }
1354:
1355: return recordCount;
1356: }
1357:
1358: /**
1359: get record count without checking for latch
1360: */
1361: protected abstract int internalDeletedRecordCount();
1362:
1363: /**
1364: get record count without checking for latch
1365: */
1366: protected int internalNonDeletedRecordCount() {
1367: // deallocated or freed page, don't count
1368: if (pageStatus != VALID_PAGE)
1369: return 0;
1370:
1371: int deletedCount = internalDeletedRecordCount();
1372:
1373: if (deletedCount == -1) {
1374: int count = 0;
1375: int maxSlot = recordCount;
1376: for (int slot = FIRST_SLOT_NUMBER; slot < maxSlot; slot++) {
1377: if (!isDeletedOnPage(slot))
1378: count++;
1379: }
1380: return count;
1381:
1382: } else {
1383:
1384: if (SanityManager.DEBUG) {
1385: int delCount = 0;
1386: int maxSlot = recordCount;
1387: for (int slot = FIRST_SLOT_NUMBER; slot < maxSlot; slot++) {
1388: if (recordHeaderOnDemand(slot).isDeleted())
1389: delCount++;
1390: }
1391: if (delCount != deletedCount)
1392: SanityManager
1393: .THROWASSERT("incorrect deleted row count. Should be: "
1394: + delCount
1395: + ", instead got: "
1396: + deletedCount
1397: + ", maxSlot = "
1398: + maxSlot
1399: + ", recordCount = "
1400: + recordCount);
1401: }
1402:
1403: return (recordCount - deletedCount);
1404: }
1405: }
1406:
1407: /** @see Page#nonDeletedRecordCount */
1408: public int nonDeletedRecordCount() {
1409: if (SanityManager.DEBUG) {
1410: SanityManager.ASSERT(isLatched());
1411: }
1412:
1413: return internalNonDeletedRecordCount();
1414:
1415: }
1416:
1417: /**
1418: * Is this page/deleted row a candidate for immediate reclaim space.
1419: * <p>
1420: * Used by access methods after executing a delete on "slot_just_deleted"
1421: * to ask whether a post commit should be queued to try to reclaim space
1422: * after the delete commits.
1423: * <p>
1424: * Will return true if the number of non-deleted rows on the page is
1425: * <= "num_non_deleted_rows". For instance 0 means schedule reclaim
1426: * only if all rows are deleted, 1 if all rows but one are deleted.
1427: * <p>
1428: * Will return true if the row just deleted is either a long row or long
1429: * column. In this case doing a reclaim space on the single row may
1430: * reclaim multiple pages of free space, so better to do it now rather
1431: * than wait for all rows on page to be deleted. This case is to address
1432: * the worst case scenario of all rows with long columns, but very short
1433: * rows otherwise. In this case there could be 1000's of rows on the
1434: * main page with many gigabytes of data on overflow pages in deleted space
1435: * that would not be reclaimed until all rows on the page were deleted.
1436: *
1437: * @return true if a reclaim space should be scheduled post commit on this
1438: * page, false otherwise.
1439: *
1440: * @param num_non_deleted_rows threshold number of non-deleted rows to
1441: * schedule reclaim space.
1442: * @param slot_just_deleted row on page to check for long row/long column
1443: *
1444: * @exception StandardException Standard exception policy.
1445: **/
1446: public boolean shouldReclaimSpace(int num_non_deleted_rows,
1447: int slot_just_deleted) throws StandardException {
1448: if (SanityManager.DEBUG) {
1449: SanityManager.ASSERT(isLatched());
1450: }
1451:
1452: boolean ret_val = false;
1453:
1454: if (internalNonDeletedRecordCount() <= num_non_deleted_rows) {
1455: ret_val = true;
1456: } else {
1457: if (!entireRecordOnPage(slot_just_deleted)) {
1458: ret_val = true;
1459: }
1460: }
1461:
1462: return (ret_val);
1463: }
1464:
1465: // no need to check for slot on page, call already checked
1466: protected final boolean isDeletedOnPage(int slot) {
1467: return getHeaderAtSlot(slot).isDeleted();
1468: }
1469:
1470: /** @see Page#isDeletedAtSlot
1471: @exception StandardException Standard exception policy.
1472: */
1473: public boolean isDeletedAtSlot(int slot) throws StandardException {
1474: if (SanityManager.DEBUG) {
1475: SanityManager.ASSERT(isLatched());
1476: }
1477:
1478: checkSlotOnPage(slot);
1479:
1480: return isDeletedOnPage(slot);
1481: }
1482:
1483: /**
1484: Set the aux object.
1485:
1486: <BR> MT - single thread required. Calls via the Page interface will have the
1487: page latched, thus providing single threadedness. Otherwise calls via this class
1488: are only made when the class has no-identity, thus only a single thread can see the object.
1489:
1490: @see Page#setAuxObject
1491: */
1492: public void setAuxObject(AuxObject obj) {
1493: if (SanityManager.DEBUG) {
1494: SanityManager.ASSERT((identity == null) || isLatched());
1495: }
1496:
1497: if (auxObj != null) {
1498: auxObj.auxObjectInvalidated();
1499: }
1500:
1501: auxObj = obj;
1502: }
1503:
1504: /**
1505: Get the aux object.
1506: <BR> MT - latched - It is required the caller throws away the returned reference
1507: when the page is unlatched.
1508:
1509: @see Page#getAuxObject
1510: */
1511: public AuxObject getAuxObject() {
1512: if (SanityManager.DEBUG) {
1513: SanityManager.ASSERT(isLatched());
1514: }
1515:
1516: return auxObj;
1517: }
1518:
1519: /*
1520: ** Methods from Lockable, just require a single exclusive locker
1521: */
1522:
1523: /**
1524: Latch me.
1525: <BR>
1526: MT - single thread required (methods of Lockable)
1527: @see Lockable#lockEvent
1528: */
1529: public void lockEvent(Latch lockInfo) {
1530: if (SanityManager.DEBUG) {
1531: SanityManager.ASSERT(owner == null,
1532: "Should only be called when not locked");
1533: }
1534:
1535: synchronized (this ) {
1536:
1537: myLatch = lockInfo;
1538:
1539: // Move page state from UNLATCHED to PRELATCH, setExclusiveNo*()
1540: // routines do the work of completing the latch - using the
1541: // preLatch status. This is so that
1542: // we don't have to wait for a clean() initiated I/O here while
1543: // holding the locking system monitor.
1544: (owner = (BaseContainerHandle) lockInfo.getQualifier())
1545: .addObserver(this );
1546: preLatch = true;
1547: }
1548: }
1549:
1550: /**
1551: Is another request compatible, no never.
1552: <BR> MT - single thread required (methods of Lockable)
1553: @see Lockable#requestCompatible
1554: */
1555: public boolean requestCompatible(Object requestedQualifier,
1556: Object grantedQualifier) {
1557: if (SanityManager.DEBUG) {
1558: SanityManager.ASSERT(owner != null,
1559: "Should only be called when locked");
1560: }
1561:
1562: return false;
1563: }
1564:
1565: /**
1566: Is another request compatible, no never.
1567: <BR> MT - single thread required (methods of Lockable)
1568: @see Lockable#requestCompatible
1569: */
1570: public boolean lockerAlwaysCompatible() {
1571: if (SanityManager.DEBUG) {
1572: SanityManager.ASSERT(owner != null,
1573: "Should only be called when locked");
1574: }
1575:
1576: return false;
1577: }
1578:
1579: /**
1580: Unlatch me, only to be called from lock manager.
1581: <BR> MT - single thread required (methods of Lockable)
1582:
1583: @see Lockable#requestCompatible
1584: */
1585: public void unlockEvent(Latch lockInfo) {
1586: if (SanityManager.DEBUG) {
1587: SanityManager.ASSERT(owner != null,
1588: "Should only be called when locked");
1589: }
1590:
1591: synchronized (this ) {
1592:
1593: if (SanityManager.DEBUG) {
1594: if (nestedLatch != 0)
1595: SanityManager
1596: .THROWASSERT("nestedLatch is non-zero on unlockEvent - value = "
1597: + nestedLatch);
1598: }
1599:
1600: owner.deleteObserver(this );
1601: owner = null;
1602: myLatch = null;
1603: if (inClean)
1604: notifyAll();
1605: }
1606: }
1607:
1608: /*
1609: ** Methods of Observer.
1610: */
1611:
1612: /**
1613: This object is set to observe the BaseContainerHandle it was obtained by,
1614: that handle will notify its observers when it is being closed. In that case
1615: we will release the latch on the page held by that container.
1616:
1617: <BR>
1618: MT - latched
1619:
1620: @see Observer#update
1621: */
1622:
1623: public void update(Observable obj, Object arg) {
1624:
1625: if (SanityManager.DEBUG) {
1626: SanityManager.ASSERT(isLatched());
1627: SanityManager.ASSERT(obj == owner);
1628: }
1629:
1630: releaseExclusive();
1631: }
1632:
1633: /*
1634: ** Implementation specific methods
1635: */
1636:
1637: /**
1638: Get the Page identifer
1639:
1640: <BR> MT - RESOLVE
1641: */
1642: public PageKey getPageId() {
1643: if (SanityManager.DEBUG) {
1644: SanityManager.ASSERT(identity != null);
1645: }
1646:
1647: return identity;
1648: }
1649:
1650: /**
1651: Get an exclusive latch on the page.
1652: <BR>
1653: MT - thread safe
1654: @exception StandardException Standard Cloudscape policy.
1655: */
1656: public void setExclusive(BaseContainerHandle requester)
1657: throws StandardException {
1658:
1659: RawTransaction t = requester.getTransaction();
1660:
1661: // In some cases latches are held until after a commit or an abort
1662: // (currently internal and nested top transactions.
1663: // If this is the case then during an abort a latch
1664: // request will be made for a latch that is already held.
1665: // We do not allow the latch to be obtained multiple times
1666: // because i) lock manager might assume latches are exclusive for
1667: // performance, ii) holding a page latched means that the page is
1668: // on the container handle's obervers list, if we latched it twice
1669: // then the paeg would have to be on the list twice, which is not supported
1670: // since the page has value equality. To be on the list twice reference
1671: // equality would be required, which would mean pushing a ReferenceObservable
1672: // object for every latch; iii) other unknown reasons :-)
1673: synchronized (this ) {
1674: // need synchronized block because owner may be set to null in the
1675: // middle if another thread is in the process of unlatching the
1676: // page
1677: if ((owner != null) && (t == owner.getTransaction())) {
1678:
1679: if (t.inAbort()) {
1680: //
1681: nestedLatch++;
1682: return;
1683: }
1684: }
1685: // just deadlock out ...
1686: }
1687:
1688: // Latch the page, owner is set through the Lockable call backs.
1689: t.getLockFactory().latchObject(t, this , requester,
1690: C_LockFactory.WAIT_FOREVER);
1691:
1692: // latch granted, but cleaner may "own" the page.
1693:
1694: if (SanityManager.DEBUG) {
1695: SanityManager.ASSERT(isLatched(), "page not latched");
1696: }
1697:
1698: synchronized (this ) {
1699: // lockEvent() will grant latch, even if cleaner "owns" the page.
1700: // Wait here unil cleaner is done. This is safe as now we own the
1701: // latch, and have yet to do anything to the in-memory data
1702: // structures.
1703: //
1704: // Previously we would wait in lockEvent, but that caused the code
1705: // to block on I/O while holding the locking system monitor.
1706:
1707: while (inClean) {
1708: try {
1709: // Expect notify from clean() routine.
1710: wait();
1711: } catch (InterruptedException ie) {
1712: }
1713: }
1714:
1715: // no clean taking place, so safe to move to full LATCHED state.
1716: preLatch = false;
1717: }
1718:
1719: }
1720:
1721: /**
1722: Get an exclusive latch on the page, but only if I don't have to wait.
1723: <BR>
1724: MT - thread safe
1725: */
1726: boolean setExclusiveNoWait(BaseContainerHandle requester)
1727: throws StandardException {
1728:
1729: RawTransaction t = requester.getTransaction();
1730:
1731: // comment in setExclusive()
1732: synchronized (this ) {
1733: if ((owner != null) && (t == owner.getTransaction())) {
1734:
1735: if (t.inAbort()) {
1736: //
1737: nestedLatch++;
1738: return true;
1739: }
1740: }
1741: // just deadlock out ...
1742: }
1743:
1744: // Latch the page, owner is set through the Lockable call backs.
1745: boolean gotLatch = t.getLockFactory().latchObject(t, this ,
1746: requester, C_LockFactory.NO_WAIT);
1747: if (!gotLatch)
1748: return false;
1749:
1750: synchronized (this ) {
1751: // lockEvent() will grant latch, even if cleaner "owns" the page.
1752: // Wait here unil cleaner is done. This is safe as now we own the
1753: // latch, and have yet to do anything to the in-memory data
1754: // structures.
1755: //
1756: // Previously we would wait in lockEvent, but that caused the code
1757: // to block on I/O while holding the locking system monitor.
1758:
1759: while (inClean) {
1760: //if (SanityManager.DEBUG)
1761: // SanityManager.DEBUG_PRINT("setExclusiveNoWait", "in while loop.");
1762:
1763: try {
1764: // Expect notify from clean() routine.
1765: wait();
1766: } catch (InterruptedException ie) {
1767: }
1768: }
1769:
1770: // no clean taking place, so safe to move to full LATCHED state.
1771: preLatch = false;
1772: }
1773:
1774: if (SanityManager.DEBUG) {
1775: SanityManager.ASSERT(isLatched(), "page not latched");
1776: }
1777:
1778: return true;
1779: }
1780:
1781: /**
1782: Release the exclusive latch on the page.
1783: <BR>
1784: MT - latched
1785: */
1786: protected void releaseExclusive() /* throws StandardException */{
1787:
1788: if (SanityManager.DEBUG) {
1789: if (!isLatched()) {
1790: SanityManager
1791: .THROWASSERT("releaseExclusive failed, nestedLatch = "
1792: + nestedLatch);
1793: }
1794: }
1795:
1796: if (nestedLatch > 0) {
1797: nestedLatch--;
1798: return;
1799: }
1800:
1801: RawTransaction t = owner.getTransaction();
1802: t.getLockFactory().unlatch(myLatch);
1803: }
1804:
1805: /*
1806: ** Manipulation of the in-memory version of the slot table.
1807: */
1808:
1809: /**
1810: Must be called by any non-abstract sub-class to initialise the slot
1811: table.
1812: */
1813:
1814: protected final void setHeaderAtSlot(int slot, StoredRecordHeader rh) {
1815:
1816: if (slot < headers.length) {
1817: // check that array "cache" of headers is big enough.
1818: if (rh != null) {
1819: headers[slot] = rh;
1820: }
1821: } else {
1822: // need to grow the array, just allocate new array and copy.
1823: StoredRecordHeader[] new_headers = new StoredRecordHeader[slot + 1];
1824:
1825: System
1826: .arraycopy(headers, 0, new_headers, 0,
1827: headers.length);
1828:
1829: headers = new_headers;
1830:
1831: headers[slot] = rh;
1832: }
1833: }
1834:
1835: protected final void bumpRecordCount(int number) {
1836: recordCount += number;
1837: }
1838:
1839: public final StoredRecordHeader getHeaderAtSlot(int slot) {
1840:
1841: if (slot < headers.length) {
1842: StoredRecordHeader rh = headers[slot];
1843:
1844: return ((rh != null) ? rh : recordHeaderOnDemand(slot));
1845: } else {
1846: return recordHeaderOnDemand(slot);
1847: }
1848: }
1849:
1850: /**
1851: Returns true if the entire record of that slot fits inside of this
1852: page. Returns false if part of the record on this slot overflows to
1853: other pages, either due to long row or long column.
1854:
1855: <BR>
1856: MT - latched
1857:
1858: @exception StandardException Standard Cloudscape error policy
1859: */
1860: public abstract boolean entireRecordOnPage(int slot)
1861: throws StandardException;
1862:
1863: public abstract StoredRecordHeader recordHeaderOnDemand(int slot);
1864:
1865: /**
1866: Is the given slot number on the page?
1867:
1868: <BR>
1869: MT - latched
1870: */
1871: private final void checkSlotOnPage(int slot)
1872: throws StandardException {
1873:
1874: if (SanityManager.DEBUG) {
1875: SanityManager.ASSERT(isLatched());
1876: }
1877:
1878: if (slot >= FIRST_SLOT_NUMBER && slot < recordCount) {
1879: return;
1880: }
1881:
1882: throw StandardException
1883: .newException(SQLState.DATA_SLOT_NOT_ON_PAGE);
1884: }
1885:
1886: /**
1887: Mark the record at the passed in slot as deleted.
1888:
1889: return code comes from StoredRecordHeader class:
1890: return 1, if delete status from not deleted to deleted
1891: return -1, if delete status from deleted to not deleted
1892: return 0, if status unchanged.
1893: <BR>
1894: <B>Any sub-class must call this method when deleting a record.</B>
1895:
1896: <BR>
1897: MT - latched
1898:
1899: @exception StandardException Standard Cloudscape error policy
1900: @exception IOException IO error accessing page
1901: */
1902: public int setDeleteStatus(int slot, boolean delete)
1903: throws StandardException, IOException {
1904:
1905: if (SanityManager.DEBUG) {
1906: // latch check performed in checkSlotOnPage
1907: checkSlotOnPage(slot);
1908: ;
1909: }
1910:
1911: return (getHeaderAtSlot(slot).setDeleted(delete));
1912: }
1913:
1914: /**
1915: Mark this page as being deallocated
1916:
1917: @exception StandardException Cloudscape Standard error policy
1918: */
1919: public void deallocatePage() throws StandardException {
1920: if (SanityManager.DEBUG) {
1921: SanityManager.ASSERT(isLatched());
1922: }
1923:
1924: if (!owner.updateOK()) {
1925: throw StandardException
1926: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1927: }
1928:
1929: RawTransaction t = owner.getTransaction();
1930:
1931: owner.getActionSet().actionInvalidatePage(t, this );
1932: }
1933:
1934: /**
1935: Mark this page as being allocated and initialize it to a pristine page
1936: @exception StandardException Cloudscape Standard error policy
1937: */
1938: public void initPage(int initFlag, long pageOffset)
1939: throws StandardException {
1940: if (SanityManager.DEBUG) {
1941: SanityManager.ASSERT(isLatched());
1942: }
1943:
1944: if (!owner.updateOK()) {
1945: throw StandardException
1946: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
1947: }
1948:
1949: RawTransaction t = owner.getTransaction();
1950:
1951: owner.getActionSet().actionInitPage(t, this , initFlag,
1952: getTypeFormatId(), pageOffset);
1953: }
1954:
1955: /**
1956: Find the slot for the record with the passed in identifier.
1957:
1958: <BR>
1959: This method returns the record regardless of its deleted status.
1960: <BR>
1961: The "slotHint" argument is a hint about what slot the record id might
1962: be in. Callers may save the last slot where the record was across
1963: latch/unlatches to the page, and then pass that slot back as a hint -
1964: if the page has not shuffled slots since the last reference then the
1965: hint will succeed and a linear search is saved. If the caller has
1966: no idea where it may be, then FIRST_SLOT_NUMBER is passed in and a
1967: linear search is performed.
1968: <BR>
1969: MT - latched
1970:
1971: @param recordId record id of the record to search for.
1972: @param slotHint "hint" about which slot the record might be in.
1973:
1974: */
1975: public int findRecordById(int recordId, int slotHint) {
1976:
1977: if (SanityManager.DEBUG) {
1978: SanityManager.ASSERT(isLatched());
1979: }
1980:
1981: if (slotHint == FIRST_SLOT_NUMBER)
1982: slotHint = recordId - RecordHandle.FIRST_RECORD_ID;
1983:
1984: int maxSlot = recordCount();
1985:
1986: if ((slotHint > FIRST_SLOT_NUMBER) && (slotHint < maxSlot)
1987: && (recordId == getHeaderAtSlot(slotHint).getId())) {
1988: return (slotHint);
1989: } else {
1990: for (int slot = FIRST_SLOT_NUMBER; slot < maxSlot; slot++) {
1991: if (recordId == getHeaderAtSlot(slot).getId()) {
1992: return slot;
1993: }
1994: }
1995: }
1996:
1997: return -1;
1998: }
1999:
2000: /**
2001: Find the slot for the first record on the page with an id greater than
2002: the passed in identifier.
2003:
2004: <BR>
2005: Returns the slot of the first record on the page with an id greater
2006: than the one passed in. Usefulness of this functionality depends on the
2007: clients use of the raw store interfaces. If all "new" records are
2008: always inserted at the end of the page, and the raw store continues
2009: to guarantee that all record id's will be allocated in increasing order
2010: on a given page, then a page is always sorted
2011: in record id order. For instance current heap tables function this
2012: way. If the client ever inserts at a particular slot number, rather
2013: than at the "end" then the record id's will not be sorted.
2014: <BR>
2015: In the case where all record id's are always sorted on a page, then
2016: this routine can be used by scan's which "lose" their position because
2017: the row they have as a position was purged. They can reposition their
2018: scan at the "next" row after the row that is now missing from the table.
2019: <BR>
2020: This method returns the record regardless of its deleted status.
2021: <BR>
2022: MT - latched
2023:
2024: @param recordId record id of the first record on the page with a
2025: record id higher than the one passed in. If no
2026: such record exists, -1 is returned.
2027: */
2028: private int findNextRecordById(int recordId) {
2029: if (SanityManager.DEBUG) {
2030: SanityManager.ASSERT(isLatched());
2031: }
2032:
2033: int maxSlot = recordCount();
2034:
2035: for (int slot = FIRST_SLOT_NUMBER; slot < maxSlot; slot++) {
2036: if (getHeaderAtSlot(slot).getId() > recordId) {
2037: return (slot);
2038: }
2039: }
2040:
2041: return -1;
2042: }
2043:
2044: /**
2045: Copy num_rows from srcPage, src_slot into this page starting at dest_slot.
2046: This is destination page of the the copy half of copy and Purge.
2047:
2048: @see Page#copyAndPurge
2049: */
2050: private void copyInto(BasePage srcPage, int src_slot, int num_rows,
2051: int dest_slot) throws StandardException {
2052: if ((dest_slot < 0) || dest_slot > recordCount) {
2053: throw StandardException
2054: .newException(SQLState.DATA_SLOT_NOT_ON_PAGE);
2055: }
2056:
2057: RawTransaction t = owner.getTransaction();
2058:
2059: // get num_rows row locks, need to predict what those recordIds will be
2060:
2061: int[] recordIds = new int[num_rows];
2062:
2063: PageKey pageId = getPageId(); // RESOLVE - MT problem ?
2064:
2065: // get new recordIds for the rows from this page
2066: // RESOLVE: we should also record the amount of reserved space
2067:
2068: for (int i = 0; i < num_rows; i++) {
2069: if (i == 0)
2070: recordIds[i] = newRecordId();
2071: else
2072: recordIds[i] = newRecordId(recordIds[i - 1]);
2073:
2074: RecordHandle handle = new RecordId(pageId, recordIds[i], i);
2075: owner.getLockingPolicy().lockRecordForWrite(t, handle,
2076: false, true);
2077: }
2078:
2079: // RESOLVE: need try block here to invalidate self and crash the system
2080: owner.getActionSet().actionCopyRows(t, this , srcPage,
2081: dest_slot, num_rows, src_slot, recordIds);
2082: }
2083:
2084: /**
2085: Remove record at slot.
2086: <p>
2087: Remove the slot at the in-memory slot table, i.e.,
2088: slots from 0 to deleteSlot-1 is untouched, deleteSlot is removed from
2089: in memory slot table, deleteSlot+1 .. recordCount()-1 move to
2090: down one slot.
2091:
2092: <BR>
2093: MT - latched
2094: */
2095: protected void removeAndShiftDown(int slot) {
2096: if (SanityManager.DEBUG) {
2097: SanityManager.ASSERT(isLatched());
2098:
2099: SanityManager.ASSERT(slot >= 0 && slot < recordCount);
2100: }
2101:
2102: // just copy the records down in the array (copying over the slot
2103: // entry that is being eliminated) and null out the last entry,
2104: // it is ok for the array to be larger than necessary.
2105: //
2106: // source of copy: slot + 1
2107: // dest of copy: slot
2108: // length of copy: (length of array - source of copy)
2109: System.arraycopy(headers, slot + 1, headers, slot,
2110: headers.length - (slot + 1));
2111: headers[headers.length - 1] = null;
2112:
2113: recordCount--;
2114: }
2115:
2116: /**
2117: Shift all records in the in-memory slot table up one slot,
2118: starting at and including the record in slot 'low'
2119: A new slot is added to accomdate the move.
2120:
2121: <BR>
2122: MT - latched
2123: */
2124: protected StoredRecordHeader shiftUp(int low) {
2125:
2126: if (SanityManager.DEBUG) {
2127: SanityManager.ASSERT(isLatched());
2128:
2129: if ((low < 0) || (low > recordCount)) {
2130: SanityManager
2131: .THROWASSERT("shiftUp failed, low must be between 0 and recordCount."
2132: + " low = "
2133: + low
2134: + ", recordCount = "
2135: + recordCount + "\n page = " + this );
2136: }
2137: }
2138:
2139: if (low < headers.length) {
2140: // just copy the records up in the array (copying over the slot
2141: // entry that is being eliminated) and null out the entry at "low",
2142: // it is ok for the array to be shorter than necessary.
2143: //
2144: // This code throws away the "last" entry in
2145: // the array, which will cause a record header cache miss if it
2146: // is needed. This delays the object allocation of a new array
2147: // object until we really need that entry, vs. doing it on the
2148: // insert.
2149: //
2150: // source of copy: low
2151: // dest of copy: low + 1
2152: // length of copy: (length of array - dest of copy)
2153:
2154: // adding in the middle
2155: System.arraycopy(headers, low, headers, low + 1,
2156: headers.length - (low + 1));
2157:
2158: headers[low] = null;
2159: }
2160:
2161: return (null);
2162: }
2163:
2164: /**
2165: Try to compact this record. Deleted record are treated the same way as
2166: nondeleted record. This page must not be an overflow page. The record
2167: may already have been purged from the page.
2168:
2169: <P>
2170: <B>Locking Policy</B>
2171: <P>
2172: No locks are obtained.
2173:
2174: <BR>
2175: MT - latched
2176:
2177: <P>
2178: <B>NOTE : CAVEAT </B><BR>
2179: This operation will physically get rid of any reserved space this
2180: record may have, or it may compact the record by merging strung out row
2181: pieces together. Since the freed reserved space is immediately usable
2182: by other transactions which latched the page, it is only safe to use
2183: this operation if the caller knows that it has exclusive access to the
2184: page for the duration of the transaction, i.e., effectively holding a
2185: page lock on the page, AND that the record has no uncommitted
2186: updates.
2187:
2188: @param handle Handle to deleted or non-deleted record
2189: @see ContainerHandle#compactRecord
2190:
2191: @exception StandardException Standard Cloudscape error policy
2192: */
2193: public void compactRecord(RecordHandle handle)
2194: throws StandardException {
2195: if (SanityManager.DEBUG) {
2196: SanityManager.ASSERT(isLatched());
2197: }
2198:
2199: if (!owner.updateOK()) {
2200: throw StandardException
2201: .newException(SQLState.DATA_CONTAINER_READ_ONLY);
2202: }
2203:
2204: if (handle.getId() < RecordHandle.FIRST_RECORD_ID) {
2205: throw StandardException.newException(
2206: SQLState.DATA_INVALID_RECORD_HANDLE, handle);
2207: }
2208:
2209: if (handle.getPageNumber() != getPageNumber()) {
2210: throw StandardException.newException(
2211: SQLState.DATA_WRONG_PAGE_FOR_HANDLE, handle);
2212: }
2213:
2214: if (isOverflowPage()) {
2215: throw StandardException.newException(
2216: SQLState.DATA_UNEXPECTED_OVERFLOW_PAGE, handle);
2217: }
2218:
2219: int slot = findRecordById(handle.getId(), handle
2220: .getSlotNumberHint());
2221:
2222: if (slot >= 0) {
2223: compactRecord(owner.getTransaction(), slot, handle.getId());
2224: }
2225: // else record gone, no compaction necessary
2226: }
2227:
2228: /*
2229: ** Methods that read/store records/fields based upon calling methods
2230: ** a sub-calls provides to do the actual storage work.
2231: */
2232:
2233: /*
2234: ** Page LastLog Instant control
2235: */
2236:
2237: public final LogInstant getLastLogInstant() {
2238: return lastLog;
2239: }
2240:
2241: protected final void clearLastLogInstant() {
2242: lastLog = null;
2243: }
2244:
2245: protected final void updateLastLogInstant(LogInstant instant) {
2246: if (SanityManager.DEBUG) {
2247: SanityManager.ASSERT(isLatched());
2248: }
2249:
2250: // we should not null out the log instant even if this page is being
2251: // updated by a non-logged action, there may have been logged action
2252: // before this and we don't want to loose that pointer
2253: if (instant != null)
2254: lastLog = instant;
2255: }
2256:
2257: /*
2258: ** Page Version control
2259: */
2260:
2261: /**
2262: Return the current page version.
2263: */
2264: public final long getPageVersion() {
2265: return pageVersion;
2266: }
2267:
2268: /**
2269: increment the version by one and return the new version.
2270: */
2271: protected final long bumpPageVersion() {
2272: if (SanityManager.DEBUG) {
2273: SanityManager.ASSERT(isLatched());
2274: }
2275: return ++pageVersion;
2276: }
2277:
2278: /**
2279: set it when the page is read from disk.
2280:
2281: <BR> MT - single thread required - Only called while the page has no identity which
2282: requires that only a single caller can be accessing it.
2283: */
2284: public final void setPageVersion(long v) {
2285: pageVersion = v;
2286: }
2287:
2288: /**
2289: Set page status based on passed in status flag.
2290: */
2291: protected void setPageStatus(byte status) {
2292: pageStatus = status;
2293: }
2294:
2295: /**
2296: Get the page status, one of the values in the above page status flag
2297: */
2298: public byte getPageStatus() {
2299: return pageStatus;
2300: }
2301:
2302: /*
2303: ** abstract methods that an implementation must provide.
2304: **
2305: ** <BR> MT - latched, page is latched when these methods are called.
2306: */
2307:
2308: /**
2309: * Read the record at the given slot into the given row.
2310: * <P>
2311: * This reads and initializes the columns in the row array from the raw
2312: * bytes stored in the page associated with the given slot. If validColumns
2313: * is non-null then it will only read those columns indicated by the bit
2314: * set, otherwise it will try to read into every column in row[].
2315: * <P>
2316: * If there are more columns than entries in row[] then it just stops after
2317: * every entry in row[] is full.
2318: * <P>
2319: * If there are more entries in row[] than exist on disk, the requested
2320: * excess columns will be set to null by calling the column's object's
2321: * restoreToNull() routine
2322: * (ie. ((Object) column).restoreToNull() ).
2323: * <P>
2324: * If a qualifier list is provided then the row will only be read from
2325: * disk if all of the qualifiers evaluate true. Some of the columns may
2326: * have been read into row[] in the process of evaluating the qualifier.
2327: *
2328: * <BR> MT - latched, page is latched when this methods is called.
2329: *
2330: *
2331: * @param slot the slot number
2332: * @param row (out) filled in sparse row
2333: * @param fetchDesc A set of information about the fetch: what
2334: * columns to fetch, any qualifiers, ...
2335: * @param rh the record handle for the row at top level,
2336: * and is used in OverflowInputStream to lock the
2337: * row for Blobs/Clobs.
2338: * @param isHeadRow Is the head row portion of the row, false if
2339: * a long row and the 2-N'th portion of the long
2340: * row.
2341: *
2342: * @return false if a qualifier_list is provided and the row does not
2343: * qualifier (no row read in that case), else true.
2344: *
2345: * @exception StandardException Standard Cloudscape error policy
2346: **/
2347: protected abstract boolean restoreRecordFromSlot(int slot,
2348: Object[] row, FetchDescriptor fetchDesc, RecordHandle rh,
2349: StoredRecordHeader recordHeader, boolean isHeadRow)
2350: throws StandardException;
2351:
2352: /**
2353: Read portion of a log record at the given slot into the given byteHolder.
2354:
2355: <BR> MT - latched, page is latched when this methods is called.
2356:
2357:
2358: @exception StandardException Standard Cloudscape error policy
2359: */
2360: protected abstract void restorePortionLongColumn(
2361: OverflowInputStream fetchStream) throws StandardException,
2362: IOException;
2363:
2364: /**
2365: Create a new record identifier.
2366:
2367: <BR> MT - latched, page is latched when this methods is called.
2368:
2369: @exception StandardException Standard Cloudscape error policy
2370: */
2371: public abstract int newRecordId() throws StandardException;
2372:
2373: /**
2374: Create a new record identifier, and bump to next recordid.
2375:
2376: <BR> MT - latched, page is latched when this methods is called.
2377:
2378: @exception StandardException Standard Cloudscape error policy
2379: */
2380: public abstract int newRecordIdAndBump() throws StandardException;
2381:
2382: /**
2383: Create a new record identifier, the passed in one is the last one created.
2384: Use this method to collect and reserve multiple recordIds in one
2385: stroke. Given the same input recordId, the subclass MUST return the
2386: same recordId every time.
2387:
2388: <BR> MT - latched, page is latched when this methods is called.
2389:
2390: @exception StandardException Standard Cloudscape error policy
2391: */
2392: protected abstract int newRecordId(int recordId)
2393: throws StandardException;
2394:
2395: /**
2396: Is there space for copying this many rows which takes this many bytes
2397: on the page
2398:
2399: <BR> MT - latched, page is latched when this methods is called.
2400:
2401: @exception StandardException Standard Cloudscape policy.
2402: */
2403: public abstract boolean spaceForCopy(int num_rows, int[] spaceNeeded)
2404: throws StandardException;
2405:
2406: /**
2407: Return the total number of bytes used, reserved, or wasted by the
2408: record at this slot.
2409:
2410: <BR> MT - latched, page is latched when this methods is called.
2411:
2412: @exception StandardException Standard Cloudscape policy.
2413: */
2414: public abstract int getTotalSpace(int slot)
2415: throws StandardException;
2416:
2417: /**
2418: Return the total number of bytes reserved by the
2419: record at this slot.
2420:
2421: <BR> MT - latched, page is latched when this methods is called.
2422:
2423: @exception IOException Thrown by InputStream methods potential I/O errors
2424: */
2425: public abstract int getReservedCount(int slot) throws IOException;
2426:
2427: /*
2428: ** Methods that our super-class (BasePage) requires we implement.
2429: ** Here we only implement the methods that correspond to the logical
2430: ** operations that require logging, any other methods that are storage
2431: ** specific we leave to our sub-class.
2432: **
2433: ** All operations that are logged must bump this page's version number
2434: ** and update this page's last log instant.
2435: ** These should be sanity checked on each logAndDo (similarly, it should
2436: ** be checked in CompensationOperation.doMe)
2437: */
2438:
2439: /*
2440: ** Methods that any sub-class must implement. These allow generic log operations.
2441: */
2442:
2443: /**
2444: Get the stored length of a record. This must match the amount of data
2445: written by logColumn and logField.
2446:
2447: <BR> MT - latched - page latch must be held
2448: */
2449:
2450: public abstract int getRecordLength(int slot) throws IOException;
2451:
2452: /**
2453: Restore a storable row from a InputStream that was used to
2454: store the row after a logRecord call.
2455:
2456: <BR> MT - latched - page latch must be held
2457:
2458: @exception StandardException Standard Cloudscape error policy
2459: @exception IOException object exceeds the available data in the stream.
2460:
2461: */
2462: public abstract void restoreRecordFromStream(LimitObjectInput in,
2463: Object[] row) throws StandardException, IOException;
2464:
2465: /**
2466: Log a currently stored record to the output stream.
2467: The logged version of the record must be readable by storeRecord.
2468:
2469: <BR> MT - latched - page latch must be held
2470:
2471:
2472: @param slot Slot number the record is stored in.
2473: @param flag LOG_RECORD_*, the reason for logging the record.
2474: @param recordId Record identifier of the record.
2475: @param validColumns which columns needs to be logged
2476: @param out Where to write the logged form.
2477: @param headRowHandle the recordHandle of the head row piece, used
2478: for post commit cleanup for update.
2479:
2480: @exception StandardException Standard Cloudscape error policy
2481: */
2482: public abstract void logRecord(int slot, int flag, int recordId,
2483: FormatableBitSet validColumns, OutputStream out,
2484: RecordHandle headRowHandle) throws StandardException,
2485: IOException;
2486:
2487: /**
2488: Log the row that will be stored at the given slot to the given OutputStream.
2489: The logged form of the Row must be readable by storeRecord.
2490:
2491: <BR> MT - latched - page latch must be held
2492:
2493: @param slot Slot number the record will be stored in.
2494: @param forInsert True if the row is being logged for an insert,
2495: false for an update.
2496: @param recordId Record identifier of the record.
2497: @param row The row version of the record.
2498: @param validColumns FormatableBitSet of which columns in row are valid,
2499: null indicates all are valid
2500: @param out Where to write the logged form.
2501: @param startColumn The first column that is being logged in this row.
2502: This is used when logging portion of long rows.
2503: @param insertFlag To indicate whether the insert would allow overflow.
2504: @param realStartColumn This is used when a long column is detected.
2505: Portion of the row may already be logged and stored
2506: in the 'out' buffer. After we log the long column,
2507: the saved buffer was passed here, and we need to
2508: continue to log the row. realStartColumn is the starting
2509: column for the continuation of the logRow operation.
2510: Pass in (-1) if realStartColumn is not significant.
2511: @param realSpaceOnPage Being used in conjunction with realStartColumn,
2512: to indicate the real free space left on the page.
2513:
2514: @exception StandardException Standard Cloudscape error policy
2515: */
2516: public abstract int logRow(int slot, boolean forInsert,
2517: int recordId, Object[] row, FormatableBitSet validColumns,
2518: DynamicByteArrayOutputStream out, int startColumn,
2519: byte insertFlag, int realStartColumn, int realSpaceOnPage,
2520: int overflowThreshold) throws StandardException,
2521: IOException;
2522:
2523: /**
2524: Log a currently stored field.
2525: The logged version of the field must be readable by storeField.
2526:
2527: <BR> MT - latched - page latch must be held
2528:
2529: @param slot Slot number the record is stored in.
2530: @param fieldNumber Number of the field (starts at 0).
2531: @param out Where to write the logged form.
2532:
2533: @exception StandardException Standard Cloudscape error policy
2534: */
2535: public abstract void logField(int slot, int fieldNumber,
2536: OutputStream out) throws StandardException, IOException;
2537:
2538: /**
2539: Log a to be stored column.
2540:
2541: <BR> MT - latched - page latch must be held
2542:
2543: @param slot slot of the current record
2544: @param fieldId field number of the column being updated
2545: @param column column version of the field.
2546: @param out Where to write the logged form.
2547:
2548: @exception StandardException Standard Cloudscape error policy
2549: */
2550: public abstract void logColumn(int slot, int fieldId,
2551: Object column, DynamicByteArrayOutputStream out,
2552: int overflowThreshold) throws StandardException,
2553: IOException;
2554:
2555: /**
2556: Log a to be stored long column. return -1 when done.
2557:
2558: <BR> MT - latched - page latch must be held
2559:
2560: @param slot slot of the current record
2561: @param recordId the id of the long column record
2562: @param column column version of the field.
2563: @param out Where to write the logged form.
2564:
2565: @exception StandardException Standard Cloudscape error policy
2566: */
2567: public abstract int logLongColumn(int slot, int recordId,
2568: Object column, DynamicByteArrayOutputStream out)
2569: throws StandardException, IOException;
2570:
2571: /**
2572: Read a previously stored record written by logRecord or logRow and store
2573: it on the data page at the given slot with the given record identifier.
2574: Any previously stored record must be replaced.
2575:
2576: <BR> MT - latched - page latch must be held
2577:
2578: @exception StandardException Standard Cloudscape error policy
2579: @exception IOException Thrown by InputStream methods potential I/O errors
2580: while writing the page
2581:
2582: */
2583: public abstract void storeRecord(LogInstant instant, int slot,
2584: boolean forInsert, ObjectInput in)
2585: throws StandardException, IOException;
2586:
2587: /**
2588: Read a previously stored field written by logField or logColumn and store
2589: it on the data page at thge given slot with the given record identifier
2590: and field number. Any previously stored field is replaced.
2591:
2592: <BR> MT - latched - page latch must be held
2593:
2594: @exception StandardException Standard Cloudscape error policy
2595: @exception IOException Thrown by InputStream methods and potential I/O errors
2596: while writing the page.
2597: */
2598: public abstract void storeField(LogInstant instant, int slot,
2599: int fieldId, ObjectInput in) throws StandardException,
2600: IOException;
2601:
2602: /**
2603: Reserve the required number of bytes for the record in the specified slot.
2604:
2605: <BR> MT - latched - page latch must be held
2606:
2607: @exception StandardException Standard Cloudscape error policy
2608: @exception IOException Thrown by InputStream methods and potential I/O errors
2609: while writing the page.
2610: */
2611: public abstract void reserveSpaceForSlot(LogInstant instant,
2612: int slot, int spaceToReserve) throws StandardException,
2613: IOException;
2614:
2615: /**
2616: Skip a previously stored field written by logField or logColumn.
2617:
2618: <BR> MT - latched - page latch must be held
2619:
2620: @exception StandardException Standard Cloudscape error policy
2621: @exception IOException Thrown by InputStream methods
2622:
2623: */
2624: public abstract void skipField(ObjectInput in)
2625: throws StandardException, IOException;
2626:
2627: public abstract void skipRecord(ObjectInput in)
2628: throws StandardException, IOException;
2629:
2630: /**
2631: Set the delete status of a record from the page.
2632:
2633: <BR> MT - latched - page latch must be held
2634:
2635: @param slot the slot to delete or undelete
2636: @param delete set delete status to this value
2637:
2638: @exception StandardException Standard Cloudscape error policy
2639: @exception IOException IO error accessing page
2640: */
2641: public abstract void setDeleteStatus(LogInstant instant, int slot,
2642: boolean delete) throws StandardException, IOException;
2643:
2644: /**
2645: Purge a record from the page.
2646:
2647: <BR> MT - latched - page latch must be held
2648:
2649: @param slot the slot to purge
2650: @param recordId the id of the record that is to be purged
2651:
2652: @exception StandardException Standard Cloudscape error policy
2653: @exception IOException Thrown by potential I/O errors
2654: while writing the page.
2655: */
2656: public abstract void purgeRecord(LogInstant instant, int slot,
2657: int recordId) throws StandardException, IOException;
2658:
2659: /**
2660: Subclass implementation of compactRecord.
2661: @see BasePage#compactRecord
2662: @exception StandardException Standard Cloudscape error policy
2663: */
2664: protected abstract void compactRecord(RawTransaction t, int slot,
2665: int recordId) throws StandardException;
2666:
2667: /**
2668: Set the page status underneath a log record
2669:
2670: <BR> MT - latched - page latch must be held
2671:
2672: @param instant the log instant of the log record
2673: @param status the page status
2674:
2675: @exception StandardException Standard Cloudscape error policy
2676: */
2677: public abstract void setPageStatus(LogInstant instant, byte status)
2678: throws StandardException;
2679:
2680: /**
2681: initialize a page for the first time or for reuse
2682:
2683: All subtypes are expected to overwrite this method if it has something to clean up
2684:
2685: @exception StandardException Standard Cloudscape error policy
2686: */
2687: public abstract void initPage(LogInstant instant, byte status,
2688: int recordId, boolean overflow, boolean reuse)
2689: throws StandardException;
2690:
2691: /**
2692: Set the reserved space for this row to value.
2693: @exception StandardException Standard Cloudscape error policy
2694: */
2695: public abstract void setReservedSpace(LogInstant instant, int slot,
2696: int value) throws StandardException, IOException;
2697:
2698: /**
2699: Return true if the page is an overflow page, false if not.
2700: For implementation that don't have overflow pages, return false.
2701: */
2702: public abstract boolean isOverflowPage();
2703:
2704: /**
2705: Returns false if an insert is not to be allowed in the page.
2706: */
2707: public abstract boolean allowInsert();
2708:
2709: /**
2710: Returns true if an insert is allowed in the page and the page is
2711: relatively unfilled - let specific implementation decide what
2712: relatively unfilled means
2713: */
2714: public abstract boolean unfilled();
2715:
2716: /**
2717: Set the number of rows in the container - the page uses this to decide
2718: whether it needs to aggressive set the container's row count when it
2719: changes.
2720: */
2721: public abstract void setContainerRowCount(long count);
2722:
2723: /*
2724: * returns the page data array, that is actually written to the disk.
2725: */
2726: protected abstract byte[] getPageArray() throws StandardException;
2727:
2728: /*
2729: ** Debugging methods
2730: */
2731:
2732: /** Debugging, print slot table information */
2733: protected String slotTableToString() {
2734: String str = null;
2735:
2736: if (SanityManager.DEBUG) {
2737: StoredRecordHeader rh;
2738: str = new String();
2739:
2740: for (int slot = FIRST_SLOT_NUMBER; slot < recordCount; slot++) {
2741: rh = getHeaderAtSlot(slot);
2742: if (rh != null)
2743: str += "Slot " + slot + " recordId " + rh.getId();
2744: else
2745: str += "Slot " + slot + " null record";
2746: str += "\n";
2747: }
2748: }
2749: return str;
2750: }
2751:
2752: /**
2753: This lockable wants to participate in the Virtual Lock table.
2754: */
2755: public boolean lockAttributes(int flag, Hashtable attributes) {
2756: if (SanityManager.DEBUG) {
2757: SanityManager
2758: .ASSERT(attributes != null,
2759: "cannot call lockProperties with null attribute list");
2760: }
2761:
2762: if ((flag & VirtualLockTable.LATCH) == 0)
2763: return false;
2764:
2765: // by the time this is called, the page may be unlatched.
2766: PageKey pageId = identity;
2767:
2768: // not latched
2769: if (pageId == null)
2770: return false;
2771:
2772: attributes.put(VirtualLockTable.CONTAINERID, new Long(pageId
2773: .getContainerId().getContainerId()));
2774: attributes.put(VirtualLockTable.LOCKNAME, pageId.toString());
2775: attributes.put(VirtualLockTable.LOCKTYPE, "LATCH");
2776:
2777: // don't new unecesary things for now
2778: // attributes.put(VirtualLockTable.SEGMENTID, new Long(pageId.getContainerId().getSegmentId()));
2779: // attributes.put(VirtualLockTable.PAGENUM, new Long(pageId.getPageNumber()));
2780:
2781: return true;
2782: }
2783:
2784: }
|