| /* |
| |
| Derby - Class org.apache.derby.impl.store.raw.data.StoredPage |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.store.raw.data; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInput; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| import java.util.zip.CRC32; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.reference.MessageId; |
| import org.apache.derby.iapi.reference.SQLState; |
| import org.apache.derby.iapi.services.io.ArrayInputStream; |
| import org.apache.derby.iapi.services.io.ArrayOutputStream; |
| import org.apache.derby.iapi.services.io.CompressedNumber; |
| import org.apache.derby.iapi.services.io.DataInputUtil; |
| import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream; |
| import org.apache.derby.iapi.services.io.ErrorObjectInput; |
| import org.apache.derby.iapi.services.io.FormatIdInputStream; |
| import org.apache.derby.iapi.services.io.FormatIdOutputStream; |
| import org.apache.derby.iapi.services.io.FormatIdUtil; |
| import org.apache.derby.iapi.services.io.FormatableBitSet; |
| import org.apache.derby.iapi.services.io.LimitObjectInput; |
| import org.apache.derby.iapi.services.io.StoredFormatIds; |
| import org.apache.derby.iapi.services.io.StreamStorable; |
| import org.apache.derby.iapi.services.i18n.MessageService; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.store.access.Qualifier; |
| import org.apache.derby.iapi.store.access.RowUtil; |
| import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo; |
| import org.apache.derby.iapi.store.raw.ContainerHandle; |
| import org.apache.derby.iapi.store.raw.FetchDescriptor; |
| import org.apache.derby.iapi.store.raw.Page; |
| import org.apache.derby.iapi.store.raw.PageKey; |
| import org.apache.derby.iapi.store.raw.PageTimeStamp; |
| import org.apache.derby.iapi.store.raw.RawStoreFactory; |
| import org.apache.derby.iapi.store.raw.RecordHandle; |
| import org.apache.derby.iapi.store.raw.log.LogInstant; |
| import org.apache.derby.iapi.store.raw.xact.RawTransaction; |
| import org.apache.derby.iapi.types.DataValueDescriptor; |
| import org.apache.derby.iapi.util.ByteArray; |
| |
| |
| /** |
| StoredPage is a sub class of CachedPage that stores page data in a |
| fixed size byte array and is designed to be written out to a file |
| through a DataInput/DataOutput interface. A StoredPage can exist |
| in its clean or dirty state without the FileContainer it was created |
| from being in memory. |
| |
| <P><B>Page Format</B><BR> |
| The page is broken into five sections |
| <PRE> |
| +----------+-------------+-------------------+-------------------+----------+ |
| | formatId | page header | records | slot offset table | checksum | |
| +----------+-------------+-------------------+-------------------+----------+ |
| </PRE> |
| <BR><B>FormatId</B><BR> |
| The formatId is a 4 bytes array, it contains the format Id of this page. |
| <BR><B>Page Header</B><BR> |
| The page header is a fixed size, 56 bytes |
| <PRE> |
| 1 byte boolean is page an overflow page |
| 1 byte byte page status (a field maintained in base page) |
| 8 bytes long pageVersion (a field maintained in base page) |
| 2 bytes unsigned short number of slots in slot offset table |
| 4 bytes integer next record identifier |
| 4 bytes integer generation number of this page (Future Use) |
| 4 bytes integer previous generation of this page (Future Use) |
| 8 bytes bipLocation the location of the beforeimage page (Future Use) |
| 2 bytes unsigned short number of deleted rows on page. (new release 2.0) |
| 2 bytes unsigned short % of the page to keep free for updates |
| 2 bytes short spare for future use |
| 4 bytes long spare for future use (encryption uses to write |
| random bytes here). |
| 8 bytes long spare for future use |
| 8 bytes long spare for future use |
| |
| </PRE> |
| |
| Note that spare space has been guaranteed to be writen with "0", so |
| that future use of field should not either not use "0" as a valid data |
| item or pick 0 as a valid default value so that on the fly upgrade can |
| assume that 0 means field was never assigned. |
| |
| <BR><B>Records</B> |
| The records section contains zero or more records, the format of each record |
| follows. |
| minimumRecordSize is the minimum user record size, excluding the space we |
| use for the record header and field headers. When a record is inserted, it |
| is stored in a space at least as large as the sum of the minimumRecordSize |
| and total header size. |
| For example, |
| If minimumRecordSize is 10 bytes, |
| the user record is 7 bytes, |
| we used 5 bytes for record and field headers, |
| this record will take (10 + 5) bytes of space, extra 3 bytes is |
| put into reserve. |
| |
| If minimumRecordSize is 10 bytes, |
| user record is 17 bytes, |
| we used 5 bytes for record and field headers, |
| this record will take (17 + 5) bytes of space, no reserve space |
| here. |
| |
| minimumRecordSize is defined by user on per container basis. |
| The default for minimumRecordSize is set to 1. |
| |
| This implementation always keeps occupied bytes at the low end of the record |
| section. Thus removing (purging) a record moves all other records down, and |
| their slots are also moved down. |
| A page has no empty slot (an empty page has no slot) |
| |
| <BR><B>Record and Field Format</B> |
| |
| Record Header format is defined in the StoredRecordHeader class. |
| |
| <PRE> |
| <BR><B>Fields</B> |
| |
| 1 byte Boolean - is null, if true no more data follows. |
| 4 bytes Integer - length of field that follows (excludes these four bytes). |
| |
| StoredPage will use the static method provided by StoredFieldHeader |
| to read/write field status and field data length. |
| |
| Field Header format is defined in the StoredFieldHeader class. |
| <data> |
| |
| </PRE> |
| <BR><B>Slot Offset Table</B><BR> |
| The slot offset table is a table of 6 or 12 bytes per record, depending on |
| the pageSize being less or greater than 64K: |
| 2 bytes (unsigned short) or 4 bytes (int) page offset for the record that |
| is assigned to the slot, and 2 bytes (unsigned short) or 4 bytes (int) |
| for the length of the record on this page. |
| 2 bytes (unsigned short) or 4 bytes (int) for the length of the reserved |
| number of bytes for this record on this page. |
| First slot is slot 0. The slot table grows backwards. Slots are never |
| left empty. |
| <BR><B>Checksum</B><BR> |
| 8 bytes of a java.util.zip.CRC32 checksum of the entire's page contents |
| without the 8 bytes representing the checksum. |
| |
| <P><B>Page Access</B> |
| The page data is accessed in this class by one of three methods. |
| <OL> |
| <LI>As a byte array using pageData (field in cachedPage). This is the |
| fastest. |
| <LI>As an ArrayInputStream (rawDataIn) and ArrayOutputStream (rawDataOut), |
| this is used to set limits on any one reading the page logically. |
| <LI>Logically through rawDataIn (ArrayInputStream) and |
| logicalDataOut (FormatIdOutputStream), this provides the methods to write |
| logical data (e.g. booleans and integers etc.) and the ObjectInput |
| and ObjectOutput interfaces for DataValueDescriptor's. These logical |
| streams are constructed using the array streams. |
| </OL> |
| |
| @see java.util.zip.CRC32 |
| @see ArrayInputStream |
| @see ArrayOutputStream |
| **/ |
| |
| public class StoredPage extends CachedPage |
| { |
| /************************************************************************** |
| * static final Fields of the class |
| ************************************************************************** |
| */ |
| |
| /* |
| * typed format |
| */ |
| |
| public static final int FORMAT_NUMBER = |
| StoredFormatIds.RAW_STORE_STORED_PAGE; |
| |
| /** |
| * Return my format identifier. |
| **/ |
| public int getTypeFormatId() |
| { |
| return StoredFormatIds.RAW_STORE_STORED_PAGE; |
| } |
| |
| |
| /** |
| * Constants used to find different portions of data on the page. |
| * <p> |
| * The page is laid out as follows: |
| * The page is broken into five sections |
| * +----------+-------------+---------+-------------------+----------+ |
| * | formatId | page header | records | slot offset table | checksum | |
| * +----------+-------------+---------+-------------------+----------+ |
| * |
| * offset size section |
| * ------ ------------------- -------------------------- |
| * 0 PAGE_FORMAT_ID_SIZE formatId |
| * PAGE_FORMAT_ID_SIZE: PAGE_HEADER_SIZE (56) page header |
| * RECORD_SPACE_OFFSET: variable records |
| **/ |
| |
| |
| /** |
| * Start of page, formatId must fit in 4 bytes. |
| * <p> |
| * where the page header starts - page format is mandated by cached page |
| **/ |
| protected static final int PAGE_HEADER_OFFSET = PAGE_FORMAT_ID_SIZE; |
| |
| |
| /** |
| * Fixed size of the page header |
| **/ |
| protected static final int PAGE_HEADER_SIZE = 56; |
| |
| |
| /** |
| Start of the record storage area |
| */ |
| /** |
| * Start of the record storage area. |
| * <p> |
| * Note: a subclass may change the start of the record storage area. |
| * Don't always count on this number. |
| **/ |
| protected static final int RECORD_SPACE_OFFSET = |
| PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE; |
| |
| /** |
| * offset of the page version number |
| **/ |
| protected static final int PAGE_VERSION_OFFSET = PAGE_HEADER_OFFSET + 2; |
| |
| /** |
| * SMALL_SLOT_SIZE are for pages smaller than 64K, |
| * LARGE_SLOT_SIZE is for pages bigger than 64K. |
| **/ |
| protected static final int SMALL_SLOT_SIZE = 2; |
| protected static final int LARGE_SLOT_SIZE = 4; |
| |
| /** |
| * Size of the checksum stored on the page. |
| * |
| * The checksum is stored in the last 8 bytes of the page, the slot table |
| * grows backward up the page starting at the end of the page just before |
| * the checksum. |
| **/ |
| protected static final int CHECKSUM_SIZE = 8; |
| |
| /** |
| * OVERFLOW_POINTER_SIZE - Number of bytes to reserve for overflow pointer |
| * |
| * The overflow pointer is the pointer that the takes the place of the |
| * last column of a row if the row can't fit on the page. The pointer |
| * then points to another page where the next column of the row can be |
| * found. The overflow pointer can be bigger than a row, so when |
| * overflowing a row the code must overflow enough columns so that there |
| * is enough free space to write the row. Note this means that the |
| * minimum space a row can take on a page must allow for at least the |
| * size of the overflow pointers so that if the row is updated it can |
| * write the over flow pointer. |
| * |
| **/ |
| protected static final int OVERFLOW_POINTER_SIZE = 12; |
| |
| /** |
| * OVERFLOW_PTR_FIELD_SIZE - Number of bytes of an overflow field |
| * |
| * This is the length to reserve for either a column or row overflow |
| * pointer field. It includes the size of the field header plus the |
| * maxium length of the overflow pointer (it could be shorter due to |
| * compressed storage). |
| * |
| * The calcualtion is: |
| * |
| * OVERFLOW_PTR_FIELD_SIZE = |
| * OVERFLOW_POINTER_SIZE + |
| * sizeof(status byte) + |
| * sizeof(field length field for a field which is just an overflow ptr) |
| * |
| * |
| **/ |
| protected static final int OVERFLOW_PTR_FIELD_SIZE = |
| OVERFLOW_POINTER_SIZE + 1 + 1; |
| |
| /** |
| * In memory buffer used as scratch space for streaming columns. |
| **/ |
| ByteHolder bh = null; |
| |
| /************************************************************************** |
| * Fields of the class |
| ************************************************************************** |
| */ |
| |
| |
| /** |
| * Constants used in call to logColumn. |
| * <p> |
| * Action taken in this routine is determined by the kind of column as |
| * specified in the columnFlag: |
| * COLUMN_NONE - the column is insignificant |
| * COLUMN_FIRST - this is the first column in a logRow() call |
| * COLUMN_LONG - this is a known long column, therefore we will |
| * store part of the column on the current page and |
| * overflow the rest if necessary. |
| * COLUMN_CREATE_NULL - the column was recently added. |
| * it doesn't actually exist in the on-disk row yet. |
| * we will need to put a null in it as soon as possible. |
| * see DERBY-5679. |
| **/ |
| protected static final int COLUMN_NONE = 0; |
| protected static final int COLUMN_FIRST = 1; |
| protected static final int COLUMN_LONG = 2; |
| protected static final int COLUMN_CREATE_NULL = 3; |
| |
| |
| /** |
| * maxFieldSize is a worst case calculation for the size of a record |
| * on an empty page, with a single field, but still allow room for |
| * an overflow pointer if another field is to be added. See initSpace(). |
| * maxFieldSize is a worst case calculation for the size of a record |
| * |
| * This is used as the threshold for a long column. |
| * |
| * maxFieldSize = |
| * totalSpace - slotEntrySize - 16 - OVERFLOW_POINTER_SIZE; |
| **/ |
| private int maxFieldSize; |
| |
| |
| /** |
| * The page header is a fixed size, 56 bytes, following are variables used |
| * to access the fields in the header: |
| * <p> |
| * 1 byte boolean isOverflowPage is page an overflow page |
| * 1 byte byte pageStatus page status (field in base page) |
| * 8 bytes long pageVersion page version (field in base page) |
| * 2 bytes ushort slotsInUse number of slots in slot offset table |
| * 4 bytes integer nextId next record identifier |
| * 4 bytes integer generation generation number of this page(FUTURE USE) |
| * 4 bytes integer prevGeneration previous generation of page (FUTURE USE) |
| * 8 bytes long bipLocation the location of the BI page (FUTURE USE) |
| * 2 bytes ushort deletedRowCount number of deleted rows on page.(rel 2.0) |
| * 2 bytes long spare for future use |
| * 4 bytes long spare (encryption writes random bytes) |
| * 8 bytes long spare for future use |
| * 8 bytes long spare for future use |
| * |
| * Note that spare space has been guaranteed to be writen with "0", so |
| * that future use of field should not either not use "0" as a valid data |
| * item or pick 0 as a valid default value so that on the fly upgrade can |
| * assume that 0 means field was never assigned. |
| * |
| **/ |
| private boolean isOverflowPage; // is page an overflow page? |
| private int slotsInUse; // number of slots in slot offset table. |
| private int nextId; // next record identifier |
| private int generation; // (Future Use) generation number of this page |
| private int prevGeneration; // (Future Use) previous generation of page |
| private long bipLocation; // (Future Use) the location of the BI page |
| private int deletedRowCount; // number of deleted rows on page. |
| |
| /** |
| * Is the header in the byte array out of date wrt the fields. |
| * <p> |
| * this field must be set to true whenever one of the above header fields |
| * is modified. Ie any of (isOverflowPage, slotsInUse, nextId, generation, |
| * prevGeneration, bipLocation, deletedRowCount) |
| **/ |
| private boolean headerOutOfDate; |
| |
| /** |
| * holder for the checksum. |
| **/ |
| private CRC32 checksum; |
| |
| /** |
| * Minimum space to reserve for record portion length of row. |
| * <p> |
| * minimumRecordSize is stored in the container handle. It is used to |
| * reserve minimum space for recordPortionLength. Default is 1. To |
| * get the value from the container handle: |
| * myContainer.getMinimumRecordSize(); |
| * |
| * minimumRecordSize is the minimum user record size, excluding the space we |
| * use for the record header and field headers. When a record is inserted, |
| * it is stored in a space at least as large as the sum of the |
| * minimumRecordSize and total header size. |
| * |
| * For example, |
| * If minimumRecordSize is 10 bytes, |
| * the user record is 7 bytes, |
| * we used 5 bytes for record and field headers, |
| * this record will take (10 + 5) bytes of space, extra 3 bytes is |
| * put into reserve. |
| * |
| * If minimumRecordSize is 10 bytes, |
| * user record is 17 bytes, |
| * we used 5 bytes for record and field headers, |
| * this record will take (17 + 5) bytes of space, no reserve space |
| * here. |
| * |
| * minimumRecordSize is defined by user on per container basis. |
| * The default for minimumRecordSize is set to 1. |
| * |
| **/ |
| protected int minimumRecordSize; |
| |
| /** |
| * scratch variable used to keep track of the total user size for the row. |
| * the information is used by logRow to maintain minimumRecordSize |
| * on Page. minimumRecordSize is only considered for main data pages, |
| * therefore, the page must be latched during an insert operation. |
| **/ |
| private int userRowSize; |
| |
| /** |
| * slot field and slot entry size. |
| * <p> |
| * The size of these fields is dependant on the page size. |
| * These 2 variables should be set when pageSize is determined, and should |
| * not be changed for that page. |
| * |
| * Each slot entry contains 3 fields (slotOffet, recordPortionLength and |
| * reservedSpace) for the record the slot is pointing to. |
| * slotFieldSize is the size for each of the slot field. |
| * slotEntrySize is the total space used for a single slot entry. |
| **/ |
| private int slotFieldSize; |
| private int slotEntrySize; |
| |
| /** |
| * Offset of the first entry in the slot table. |
| * <p> |
| * Offset table is located at end of page, just before checksum. It |
| * grows backward as an array from this point toward the middle of the |
| * page. |
| * <p> |
| * slotTableOffsetToFirstEntry is the offset to the beginning of the |
| * first entry (slot[0]) in the slot table. This allows the following |
| * math to get to the offset of N'th entry in the slot table: |
| * |
| * offset of slot[N] = slotTableOffsetToFirstEntry + (N * slotEntrySize) |
| **/ |
| private int slotTableOffsetToFirstEntry; |
| |
| /** |
| * Offset of the record length entry in the 1st slot table entry. |
| * <p> |
| * Offset table is located at end of page, just before checksum. It |
| * grows backward as an array from this point toward the middle of the |
| * page. The record length is stored as the second "field" of the |
| * slot table entry. |
| * <p> |
| * slotTableOffsetToFirstRecordLengthField is the offset to the beginning |
| * of the record length field in the first entry (slot[0]) in the slot |
| * table. This allows the following |
| * math to get to the record length field of N'th entry in the slot table: |
| * |
| * offset of record length of slot[N] slot entry = |
| * slotTableOffsetToFirstRecordLengthField + (N * slotEntrySize) |
| **/ |
| private int slotTableOffsetToFirstRecordLengthField; |
| |
| |
| /** |
| * Offset of the reserved space length entry in the 1st slot table entry. |
| * <p> |
| * Offset table is located at end of page, just before checksum. It |
| * grows backward as an array from this point toward the middle of the |
| * page. The reserved space length is stored as the third "field" of the |
| * slot table entry. |
| * <p> |
| * slotTableOffsetToFirstReservedSpaceField is the offset to the beginning |
| * of the reserved space field in the first entry (slot[0]) in the slot |
| * table. This allows the following |
| * math to get to the reserved space field of N'th entry in the slot table: |
| * |
| * offset of reserved space of slot[N] slot entry = |
| * slotTableOffsetToFirstReservedSpaceField + (N * slotEntrySize) |
| **/ |
| private int slotTableOffsetToFirstReservedSpaceField; |
| |
| /** |
| * total usable space on a page. |
| * <p> |
| * This is the space not taken by page hdr, page table, and existing |
| * slot entries/rows. |
| **/ |
| protected int totalSpace; // total usable space on a page |
| |
| // freeSpace and firstFreeByte are initliazed to a minimum value. |
| protected int freeSpace = Integer.MIN_VALUE; // free space on the page |
| private int firstFreeByte = Integer.MIN_VALUE; // 1st free byte on page |
| |
| |
| /** |
| * % of page to keep free for updates. |
| * <p> |
| * How much of a head page should be reserved as "free" so that the space |
| * can be used by update which expands the row without needing to overflow |
| * it. 1 means save 1% of the free space for expansion. |
| **/ |
| protected int spareSpace; |
| |
| /** |
| * Scratch variable used when you need a overflowRecordHeader. Declared |
| * globally so that object is only allocated once per page. |
| **/ |
| private StoredRecordHeader overflowRecordHeader; |
| |
| /** |
| * Input streams used to read/write bytes to/from the page byte array. |
| **/ |
| protected ArrayInputStream rawDataIn; |
| protected ArrayOutputStream rawDataOut; |
| protected FormatIdOutputStream logicalDataOut; |
| |
| |
| /************************************************************************** |
| * Constructors for This class: |
| ************************************************************************** |
| */ |
| |
| |
| /** |
| * Simple no-arg constructor for StoredPage. |
| **/ |
| public StoredPage() |
| { |
| super(); |
| } |
| |
| /************************************************************************** |
| * Private/Protected methods of This class: |
| ************************************************************************** |
| */ |
| |
| /** |
| * get scratch space for over flow record header. |
| * <p> |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| |
| private StoredRecordHeader getOverFlowRecordHeader() |
| throws StandardException |
| { |
| return( |
| overflowRecordHeader != null ? |
| overflowRecordHeader : |
| (overflowRecordHeader = new StoredRecordHeader())); |
| } |
| |
| /** |
| * Initialize the StoredPage. |
| * <p> |
| * Initialize the object, ie. perform work normally perfomed in constructor. |
| * Called by setIdentity() and createIdentity() - the Cacheable interfaces |
| * which are used to move a page in/out of cache. |
| **/ |
| protected void initialize() |
| { |
| super.initialize(); |
| |
| if (rawDataIn == null) |
| { |
| rawDataIn = new ArrayInputStream(); |
| checksum = new CRC32(); |
| } |
| |
| if (pageData != null) |
| rawDataIn.setData(pageData); |
| } |
| |
| |
| /** |
| * Create the output streams. |
| * <p> |
| * Create the output streams, these are created on demand |
| * to avoid creating unrequired objects for pages that are |
| * never modified during their lifetime in the cache. |
| * <p> |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void createOutStreams() |
| { |
| rawDataOut = new ArrayOutputStream(); |
| rawDataOut.setData(pageData); |
| |
| logicalDataOut = new FormatIdOutputStream(rawDataOut); |
| } |
| |
| /** |
| * Tie the logical output stream to a passed in OutputStream. |
| * <p> |
| * Tie the logical output stream to a passed in OutputStream with |
| * no limit as to the number of bytes that can be written. |
| **/ |
| private void setOutputStream(OutputStream out) |
| { |
| if (rawDataOut == null) |
| createOutStreams(); |
| |
| logicalDataOut.setOutput(out); |
| } |
| |
| /** |
| * Reset the logical output stream. |
| * <p> |
| * Reset the logical output stream (logicalDataOut) to be attached |
| * to the page array stream as is the norm, no limits are placed |
| * on any writes. |
| * |
| **/ |
| private void resetOutputStream() |
| { |
| |
| logicalDataOut.setOutput(rawDataOut); |
| } |
| |
| /************************************************************************** |
| * Protected Methods of CachedPage class: (create, read and write a page.) |
| ************************************************************************** |
| */ |
| |
| /** |
| * use this passed in page buffer as this object's page data. |
| * <p> |
| * The page content may not have been read in from disk yet. |
| * For pagesize smaller than 64K: |
| * Size of the record offset stored in a slot (unsigned short) |
| * Size of the record portion length stored in a slot (unsigned short) |
| * Size of the record portion length stored in a slot (unsigned short) |
| * For pagesize greater than 64K, but less than 2gig: |
| * Size of the record offset stored in a slot (int) |
| * Size of the record portion length stored in a slot (int) |
| * Size of the record portion length stored in a slot (int) |
| * <p> |
| * |
| * @param pageBuffer The array of bytes to use as the page buffer. |
| **/ |
| protected void usePageBuffer(byte[] pageBuffer) |
| { |
| pageData = pageBuffer; |
| |
| int pageSize = pageData.length; |
| if (rawDataIn != null) |
| rawDataIn.setData(pageData); |
| |
| // Note that the slotFieldSize and slotEntrySize need to be |
| // calculated BEFORE initSpace() is called, because the |
| // maxFieldSize computation in initSpace() includes these |
| // values in its calculations. (DERBY-3099) |
| slotFieldSize = calculateSlotFieldSize(pageSize); |
| slotEntrySize = 3 * slotFieldSize; |
| |
| initSpace(); |
| |
| // offset of slot table entry[0] |
| slotTableOffsetToFirstEntry = |
| (pageSize - CHECKSUM_SIZE - slotEntrySize); |
| |
| // offset of record length field in slot table entry[0] |
| slotTableOffsetToFirstRecordLengthField = |
| slotTableOffsetToFirstEntry + slotFieldSize; |
| |
| // offset of reserved space field in slot table entry[0] |
| slotTableOffsetToFirstReservedSpaceField = |
| slotTableOffsetToFirstEntry + (2 * slotFieldSize); |
| |
| if (rawDataOut != null) |
| rawDataOut.setData(pageData); |
| } |
| |
| /** |
| * Calculate the slot field size from the page size. |
| * |
| * @param pageSize page size in bytes |
| * @return slot field size in bytes |
| */ |
| private int calculateSlotFieldSize(int pageSize) { |
| if (pageSize < 65536) { |
| // slots are 2 bytes (unsigned short data type) for pages <64KB |
| return SMALL_SLOT_SIZE; |
| } else { |
| // slots are 4 bytes (int data type) for pages >=64KB |
| return LARGE_SLOT_SIZE; |
| } |
| } |
| |
| /** |
| * Create a new StoredPage. |
| * <p> |
| * Make this object represent a new page (ie. a page that never existed |
| * before, as opposed to reading in an existing page from disk). |
| * <p> |
| * |
| * @param newIdentity The key describing page (segment,container,page). |
| * @param args information stored about the page, once in the |
| * container header and passed in through the object. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void createPage(PageKey newIdentity, PageCreationArgs args) |
| throws StandardException |
| { |
| |
| spareSpace = args.spareSpace; |
| minimumRecordSize = args.minimumRecordSize; |
| |
| setPageArray(args.pageSize); |
| |
| cleanPage(); // clean up the page array |
| |
| setPageVersion(0); // page is being created for the first time |
| |
| nextId = RecordHandle.FIRST_RECORD_ID; // first record Id |
| generation = 0; |
| prevGeneration = 0; // there is no previous generation |
| bipLocation = 0L; |
| |
| createOutStreams(); |
| } |
| |
| /** |
| * Initialize the page from values in the page buffer. |
| * <p> |
| * Initialize in memory structure using the buffer in pageData. This |
| * is how a StoredPage object is intialized to represent page read in |
| * from disk. |
| * <p> |
| * |
| * @param myContainer The container to read the page in from. |
| * @param newIdentity The key representing page being read in (segment, |
| * container, page number) |
| * |
| * @exception StandardException If the page cannot be read correctly, |
| * or is inconsistent. |
| **/ |
| protected void initFromData( |
| FileContainer myContainer, |
| PageKey newIdentity) |
| throws StandardException |
| { |
| if (myContainer != null) |
| { |
| // read in info about page stored once in the container header. |
| |
| spareSpace = myContainer.getSpareSpace(); |
| minimumRecordSize = myContainer.getMinimumRecordSize(); |
| } |
| |
| // if it is null, assume spareSpace and minimumRecordSize is the |
| // same. We would only call initFromData after a restore then. |
| |
| |
| try |
| { |
| validateChecksum(newIdentity); |
| } |
| catch (StandardException se) |
| { |
| if (se.getMessageId().equals(SQLState.FILE_BAD_CHECKSUM)) |
| { |
| // it is remotely possible that the disk transfer got garbled, |
| // i.e., the page is actually fine on disk but the version we |
| // got has some rubbish on it. Double check. |
| int pagesize = getPageSize(); |
| byte[] corruptPage = pageData; |
| pageData = null; // clear this |
| |
| // set up the new page array |
| setPageArray(pagesize); |
| |
| try |
| { |
| myContainer.readPage(newIdentity.getPageNumber(), pageData); |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, newIdentity)); |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.DEBUG_CLEAR("TEST_BAD_CHECKSUM"); |
| } |
| |
| // see if this read confirms the checksum error |
| try |
| { |
| validateChecksum(newIdentity); |
| } |
| catch (StandardException sse) |
| { |
| // really bad |
| throw dataFactory.markCorrupt(se); |
| } |
| |
| // If we got here, this means the first read is bad but the |
| // second read is good. This could be due to disk I/O error or |
| // a bug in the way the file pointer is mis-managed. |
| String firstImage = pagedataToHexDump(corruptPage); |
| String secondImage = |
| (SanityManager.DEBUG) ? |
| toString() : pagedataToHexDump(corruptPage); |
| |
| throw StandardException.newException( |
| SQLState.FILE_IO_GARBLED, se, |
| newIdentity, firstImage, secondImage); |
| } |
| else |
| { |
| throw se; |
| } |
| } |
| |
| try |
| { |
| readPageHeader(); |
| initSlotTable(newIdentity); |
| } |
| catch (IOException ioe) |
| { |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, newIdentity)); |
| } |
| } |
| |
| /** |
| * Validate the check sum on the page. |
| * <p> |
| * Compare the check sum stored in the page on disk with the checksum |
| * calculated from the bytes on the page. |
| * <p> |
| * |
| * @param id The key that describes the page. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void validateChecksum(PageKey id) |
| throws StandardException |
| { |
| long onDiskChecksum; |
| |
| try |
| { |
| // read the checksum stored on the page on disk. It is stored |
| // in the last "CHECKSUM_SIZE" bytes of the page, and is a long. |
| |
| rawDataIn.setPosition(getPageSize() - CHECKSUM_SIZE); |
| onDiskChecksum = rawDataIn.readLong(); |
| } |
| catch (IOException ioe) |
| { |
| |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, id)); |
| } |
| |
| // Force the checksum to be recalculated based on the current page. |
| checksum.reset(); |
| checksum.update(pageData, 0, getPageSize() - CHECKSUM_SIZE); |
| |
| // force a bad checksum error |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("TEST_BAD_CHECKSUM")) |
| { |
| // set on disk checksum to wrong value |
| onDiskChecksum = 123456789; |
| } |
| } |
| |
| if (onDiskChecksum != checksum.getValue()) |
| { |
| // try again using new checksum object to be doubly sure |
| CRC32 newChecksum = new CRC32(); |
| newChecksum.reset(); |
| newChecksum.update(pageData, 0, getPageSize()-CHECKSUM_SIZE); |
| if (onDiskChecksum != newChecksum.getValue()) |
| { |
| throw StandardException.newException( |
| SQLState.FILE_BAD_CHECKSUM, |
| id, |
| checksum.getValue(), |
| onDiskChecksum, |
| pagedataToHexDump(pageData)); |
| } |
| else |
| { |
| // old one is bad, get rid of it |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT("old checksum gets wrong value"); |
| |
| checksum = newChecksum; |
| } |
| } |
| } |
| |
| /** |
| * Recalculate checksum and write it to the page array. |
| * <p> |
| * Recalculate the checksum of the page, and write the result back into |
| * the last bytes of the page. |
| * |
| * @exception IOException if writing to end of array fails. |
| **/ |
| protected void updateChecksum() throws IOException |
| { |
| checksum.reset(); |
| checksum.update(pageData, 0, getPageSize() - CHECKSUM_SIZE); |
| |
| rawDataOut.setPosition(getPageSize() - CHECKSUM_SIZE); |
| logicalDataOut.writeLong(checksum.getValue()); |
| } |
| |
| /** |
| * Write information about page from variables into page byte array. |
| * <p> |
| * This routine insures that all information about the page is reflected |
| * in the page byte buffer. This involves moving information from local |
| * variables into encoded version on the page in page header and checksum. |
| * <p> |
| * |
| * @param identity The key of this page. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void writePage(PageKey identity) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| // some consistency checks on fields of the page, good to check |
| // before we write them into the page. |
| |
| if ((freeSpace < 0) || |
| (firstFreeByte + freeSpace) != (getSlotOffset(slotsInUse - 1))) |
| { |
| // make sure free space is not negative and does not overlap |
| // used space. |
| |
| SanityManager.THROWASSERT( |
| "writePage detected problem in freespace and used space." |
| + "slotsInUse = " + slotsInUse |
| + ", firstFreeByte = " + firstFreeByte |
| + ", freeSpace = " + freeSpace |
| + ", slotOffset = " + (getSlotOffset(slotsInUse - 1)) |
| + ", page = " + this); |
| } |
| |
| if ((slotsInUse == 0) && |
| (firstFreeByte != (getPageSize() - totalSpace - CHECKSUM_SIZE))) |
| { |
| SanityManager.THROWASSERT("slotsInUse = " + slotsInUse |
| + ", firstFreeByte = " + firstFreeByte |
| + ", freeSpace = " + freeSpace |
| + ", slotOffset = " + (getSlotOffset(slotsInUse - 1)) |
| + ", page = " + this); |
| } |
| |
| } |
| |
| try |
| { |
| if (headerOutOfDate) |
| { |
| updatePageHeader(); |
| } |
| else |
| { |
| // page version always need to be updated if page is dirty, |
| // either do it in updatePageHeader or by itself |
| updatePageVersion(); |
| } |
| |
| updateChecksum(); |
| |
| } |
| catch (IOException ioe) |
| { |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, identity)); |
| } |
| |
| } |
| |
| /** |
| * Write out the format id of this page |
| * |
| * @param identity The key of this page. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void writeFormatId(PageKey identity) throws StandardException |
| { |
| try |
| { |
| if (rawDataOut == null) |
| createOutStreams(); |
| |
| rawDataOut.setPosition(0); |
| |
| FormatIdUtil.writeFormatIdInteger( |
| logicalDataOut, getTypeFormatId()); |
| |
| } |
| catch (IOException ioe) |
| { |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, identity)); |
| } |
| } |
| |
| |
| /************************************************************************** |
| * Protected Methods of Cacheable Interface: |
| ************************************************************************** |
| */ |
| |
| /************************************************************************** |
| * Protected OverRidden Methods of BasePage: |
| ************************************************************************** |
| */ |
| |
| /** |
| * Ensure that the page is released from the cache when it is unlatched. |
| * |
| * @see org.apache.derby.impl.store.raw.data.BasePage#releaseExclusive |
| * |
| **/ |
| protected void releaseExclusive() |
| { |
| super.releaseExclusive(); |
| |
| pageCache.release(this); |
| } |
| |
| |
| /** |
| * Return the total number of bytes used, reserved, or wasted by the |
| * record at this slot. |
| * <p> |
| * The amount of space the record on this slot is currently taking on the |
| * page. |
| * |
| * If there is any reserve space or wasted space, count that in also |
| * Do NOT count the slot entry size |
| * <p> |
| * |
| * @return The number of bytes used by the row at slot "slot". |
| * |
| * @param slot look at row at this slot. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public int getTotalSpace(int slot) |
| throws StandardException |
| { |
| try |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (getRecordOffset(slot) <= 0) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "getTotalSpace failed with getRecordOffset(" + |
| slot + ") = " + |
| getRecordOffset(slot) + " must be greater than 0." + |
| "page dump = \n" + |
| toUncheckedString()); |
| SanityManager.THROWASSERT( |
| "bad record offset found in getTotalSpace()"); |
| } |
| } |
| |
| // A slot entry looks like the following: |
| // 1st field: offset of the record on the page |
| // 2nd field: length of the record on the page |
| // 3rd field: amount of space reserved for the record to grow. |
| |
| // position the read at the beginning of the 2nd field. |
| rawDataIn.setPosition(getSlotOffset(slot) + slotFieldSize); |
| |
| // return the size of the record + size of the reserved space. |
| // the size of the fields to read is determined by slotFieldSize. |
| |
| return( |
| ((slotFieldSize == SMALL_SLOT_SIZE) ? |
| (rawDataIn.readUnsignedShort() + |
| rawDataIn.readUnsignedShort()) : |
| (rawDataIn.readInt() + |
| rawDataIn.readInt()))); |
| |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| /** |
| * Is there minimal space for insert? |
| * <p> |
| * Does quick calculation to see if average size row on this page could |
| * be inserted on the page. This is done because the actual row size |
| * being inserted isn't known until we actually copy the columns from |
| * their object form into their on disk form which is expensive. So |
| * we use this calculation so that in the normal case we only do one |
| * copy of the row directly onto the page. |
| * <p> |
| * |
| * @return true if we think the page will allow an insert, false otherwise. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public boolean spaceForInsert() |
| throws StandardException |
| { |
| // is this an empty page |
| if (slotsInUse == 0) |
| return(true); |
| |
| if (!allowInsert()) |
| return(false); |
| |
| int usedSpace = totalSpace - freeSpace; |
| int bytesPerRow = usedSpace / slotsInUse; |
| |
| return(bytesPerRow <= freeSpace); |
| } |
| |
| /** |
| * Is row guaranteed to be inserted successfully on this page? |
| * <p> |
| * Return true if this record is guaranteed to be inserted successfully |
| * using insert() or insertAtSlot(). This guarantee is only valid while |
| * the row remains unchanged and the page latch is held. |
| * <p> |
| * |
| * @return bolean indicating if row can be inserted on this page. |
| * |
| * @param row The row to check for insert. |
| * @param validColumns bit map to interpret valid columns in row. |
| * @param overflowThreshold The percentage of the page to use for the |
| * insert. 100 means use 100% of the page, |
| * 50 means use 50% of page (ie. make sure |
| * 2 rows fit per page). |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public boolean spaceForInsert( |
| Object[] row, |
| FormatableBitSet validColumns, |
| int overflowThreshold) |
| throws StandardException |
| { |
| |
| // is this an empty page |
| if (slotsInUse == 0) |
| return true; |
| |
| // does the estimate think it won't fit, if not return false to avoid |
| // cost of calling logRow() just to figure out if the row will fit. |
| if (!allowInsert()) |
| return false; |
| |
| DynamicByteArrayOutputStream out = new DynamicByteArrayOutputStream(); |
| |
| try |
| { |
| // This is a public call, start column is rawstore only. |
| // set the starting Column for the row to be 0. |
| logRow( |
| 0, true, nextId, row, validColumns, out, |
| 0, Page.INSERT_DEFAULT, -1, -1, overflowThreshold); |
| |
| } |
| catch (NoSpaceOnPage nsop) |
| { |
| return false; |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Is row guaranteed to be inserted successfully on this page? |
| * <p> |
| * Return true if this record is guaranteed to be inserted successfully |
| * using insert() or insertAtSlot(). This guarantee is only valid while |
| * the row remains unchanged and the page latch is held. |
| * <p> |
| * This is a private call only used when calculating whether an overflow |
| * page can be used to insert part of an overflow row/column. |
| * |
| * @return bolean indicating if row can be inserted on this page. |
| * |
| * @param row The row to check for insert. |
| * @param validColumns bit map to interpret valid columns in row. |
| * @param overflowThreshold The percentage of the page to use for the |
| * insert. 100 means use 100% of the page, |
| * 50 means use 50% of page (ie. make sure |
| * 2 rows fit per page). |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private boolean spaceForInsert( |
| Object[] row, |
| FormatableBitSet validColumns, |
| int spaceNeeded, |
| int startColumn, |
| int overflowThreshold) |
| throws StandardException |
| { |
| if (!(spaceForInsert() && (freeSpace >= spaceNeeded))) |
| return false; |
| |
| DynamicByteArrayOutputStream out = new DynamicByteArrayOutputStream(); |
| |
| try |
| { |
| logRow( |
| 0, true, nextId, row, validColumns, out, startColumn, |
| Page.INSERT_DEFAULT, -1, -1, overflowThreshold); |
| |
| } |
| catch (NoSpaceOnPage nsop) |
| { |
| return false; |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Is this page unfilled? |
| * <p> |
| * Returns true if page is relatively unfilled, |
| * which means the page is < 1/2 full and has enough space to insert an |
| * "average" sized row onto the page. |
| * <p> |
| * |
| * @return true if page is relatively unfilled. |
| **/ |
| public boolean unfilled() |
| { |
| return (allowInsert() && (freeSpace > (getPageSize() / 2))); |
| } |
| |
| /** |
| * Is there enough space on the page to insert a minimum size row? |
| * <p> |
| * Calculate whether there is enough space on the page to insert a |
| * minimum size row. The calculation includes maintaining the required |
| * reserved space on the page for existing rows to grow on the page. |
| * <p> |
| * |
| * @return boolean indicating if a minimum sized row can be inserted. |
| **/ |
| public boolean allowInsert() |
| { |
| // is this an empty page |
| if (slotsInUse == 0) |
| return true; |
| |
| int spaceAvailable = freeSpace; |
| |
| spaceAvailable -= slotEntrySize; // need to account new slot entry |
| |
| if ((spaceAvailable < minimumRecordSize) || |
| (spaceAvailable < StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE)) |
| { |
| return false; |
| } |
| |
| // see that we reserve enough space for existing rows to grow on page |
| if (((spaceAvailable * 100) / totalSpace) < spareSpace) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * Does this page have enough space to insert the input rows? |
| * <p> |
| * Can the rows with lengths spaceNeeded[0..num_rows-1] be copied onto |
| * this page? |
| * <p> |
| * |
| * @return true if the sum of the lengths will fit on the page. |
| * |
| * @param num_rows number of rows to check for. |
| * @param spaceNeeded array of lengths of the rows to insert. |
| **/ |
| public boolean spaceForCopy(int num_rows, int[] spaceNeeded) |
| { |
| // determine how many more bytes are needed for the slot entries |
| int bytesNeeded = slotEntrySize * num_rows; |
| |
| for (int i = 0; i < num_rows; i++) |
| { |
| if (spaceNeeded[i] > 0) |
| { |
| // add up the space needed by the rows, add in minimumRecordSize |
| // if length of actual row is less than minimumRecordSize. |
| |
| bytesNeeded += |
| (spaceNeeded[i] >= minimumRecordSize ? |
| spaceNeeded[i] : minimumRecordSize); |
| } |
| } |
| |
| return((freeSpace - bytesNeeded) >= 0); |
| } |
| |
| /** |
| * Does this page have enough space to move the row to it. |
| * <p> |
| * Calculate if a row of length "spaceNeeded" with current record id |
| * "source_id" will fit on this page. |
| * |
| * @param spaceNeeded length of the row encoded with source_id record id. |
| * @param source_id record id of the row being moved. |
| * |
| * @return true if the record will fit on this page, after being given a |
| * new record id as the next id on this page. |
| **/ |
| protected boolean spaceForCopy( |
| int spaceNeeded, |
| int source_id) |
| { |
| spaceNeeded = |
| spaceNeeded |
| - StoredRecordHeader.getStoredSizeRecordId(source_id) |
| + StoredRecordHeader.getStoredSizeRecordId(nextId); |
| |
| // add up the space needed by the rows, add in minimumRecordSize |
| // if length of actual row is less than minimumRecordSize. |
| int bytesNeeded = slotEntrySize + |
| (spaceNeeded >= minimumRecordSize ? |
| spaceNeeded : minimumRecordSize); |
| |
| return((freeSpace - bytesNeeded) >= 0); |
| } |
| |
| /** |
| * Read the record at the given slot into the given row. |
| * <P> |
| * This reads and initializes the columns in the row array from the raw |
| * bytes stored in the page associated with the given slot. If validColumns |
| * is non-null then it will only read those columns indicated by the bit |
| * set, otherwise it will try to read into every column in row[]. |
| * <P> |
| * If there are more columns than entries in row[] then it just stops after |
| * every entry in row[] is full. |
| * <P> |
| * If there are more entries in row[] than exist on disk, the requested |
| * excess columns will be set to null by calling the column's object's |
| * restoreToNull() routine (ie. ((Object) column).restoreToNull() ). |
| * <P> |
| * If a qualifier list is provided then the row will only be read from |
| * disk if all of the qualifiers evaluate true. Some of the columns may |
| * have been read into row[] in the process of evaluating the qualifier. |
| * <p> |
| * This routine should only be called on the head portion of a row, it |
| * will call a utility routine to read the rest of the row if it is a |
| * long row. |
| * |
| * |
| * @param slot the slot number |
| * @param row (out) filled in sparse row |
| * @param fetchDesc Information describing fetch, including what |
| * columns to fetch and qualifiers. |
| * @param recordToLock the record handle for the row at top level, |
| * and is used in OverflowInputStream to lock the |
| * row for Blobs/Clobs. |
| * @param isHeadRow The row on this page includes the head record |
| * handle. Will be false for the overflow portions |
| * of a "long" row, where columns of a row span |
| * multiple pages. |
| * |
| * @return false if a qualifier_list is provided and the row does not |
| * qualifier (no row read in that case), else true. |
| * |
| * @exception StandardException Standard Derby error policy |
| **/ |
| protected boolean restoreRecordFromSlot( |
| int slot, |
| Object[] row, |
| FetchDescriptor fetchDesc, |
| RecordHandle recordToLock, |
| StoredRecordHeader recordHeader, |
| boolean isHeadRow) |
| throws StandardException |
| { |
| try |
| { |
| int offset_to_row_data = |
| getRecordOffset(slot) + recordHeader.size(); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (getRecordOffset(slot) < |
| (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE)) |
| { |
| SanityManager.THROWASSERT( |
| "Incorrect offset. offset = " + |
| getRecordOffset(slot) + |
| ", offset should be < " + |
| "(PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) = " + |
| (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) + |
| ", current slot = " + slot + |
| ", total slotsInUse = " + slotsInUse); |
| } |
| |
| SanityManager.ASSERT( |
| isHeadRow, "restoreRecordFromSlot called on a non-headrow"); |
| SanityManager.ASSERT( |
| !isOverflowPage(), |
| "restoreRecordFromSlot called on an overflow page."); |
| } |
| |
| // position the array reading stream at beginning of row data just |
| // past the record header. |
| ArrayInputStream lrdi = rawDataIn; |
| lrdi.setPosition(offset_to_row_data); |
| |
| if (!recordHeader.hasOverflow()) |
| { |
| if (isHeadRow) |
| { |
| if (fetchDesc != null && |
| fetchDesc.getQualifierList() != null) |
| { |
| fetchDesc.reset(); |
| |
| if (!qualifyRecordFromSlot( |
| row, |
| offset_to_row_data, |
| fetchDesc, |
| recordHeader, |
| recordToLock)) |
| { |
| return(false); |
| } |
| else |
| { |
| // reset position back for subsequent record read. |
| lrdi.setPosition(offset_to_row_data); |
| } |
| } |
| } |
| |
| // call routine to do the real work. Note that |
| // readRecordFromStream() may return false for non-overflow |
| // record, this is in the case where caller requests more |
| // columns than exist on disk. In that case we still return |
| // true at this point as there are no more columns that we |
| // can return. |
| if (fetchDesc != null) |
| { |
| readRecordFromArray( |
| row, |
| (fetchDesc.getValidColumns() == null) ? |
| row.length -1 : fetchDesc.getMaxFetchColumnId(), |
| fetchDesc.getValidColumnsArray(), |
| fetchDesc.getMaterializedColumns(), |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| else |
| { |
| readRecordFromArray( |
| row, |
| row.length - 1, |
| (int[]) null, |
| (int[]) null, |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| |
| return(true); |
| } |
| else |
| { |
| if (fetchDesc != null) |
| { |
| if (fetchDesc.getQualifierList() != null) |
| { |
| fetchDesc.reset(); |
| } |
| |
| readRecordFromArray( |
| row, |
| (fetchDesc.getValidColumns() == null) ? |
| row.length - 1 : fetchDesc.getMaxFetchColumnId(), |
| fetchDesc.getValidColumnsArray(), |
| fetchDesc.getMaterializedColumns(), |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| else |
| { |
| readRecordFromArray( |
| row, |
| row.length - 1, |
| (int[]) null, |
| (int[]) null, |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| |
| // call routine to loop through all the overflow portions of |
| // the row, reading it into "row". |
| while (recordHeader != null) |
| { |
| // The record is a long row, loop callng code to read the |
| // pieces of the row located in a linked list of rows on |
| // overflow pages. |
| StoredPage overflowPage = |
| getOverflowPage(recordHeader.getOverflowPage()); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (overflowPage == null) |
| SanityManager.THROWASSERT( |
| "cannot get overflow page"); |
| } |
| |
| // This call reads in the columns of the row that reside |
| // on "overflowPage", and if there is another piece it |
| // returns the recordHeader of the row on overFlowPage, |
| // from which we can find the next piece of the row. A |
| // null return means that we have read in the entire row, |
| // and are done. |
| recordHeader = |
| overflowPage.restoreLongRecordFromSlot( |
| row, |
| fetchDesc, |
| recordToLock, |
| recordHeader); |
| |
| overflowPage.unlatch(); |
| overflowPage = null; |
| } |
| |
| // for overflow rows just apply qualifiers at end for now. |
| |
| if ((fetchDesc != null) && |
| (fetchDesc.getQualifierList() != null)) |
| { |
| if (!qualifyRecordFromRow( |
| row, fetchDesc.getQualifierList())) |
| { |
| return(false); |
| } |
| } |
| |
| return(true); |
| } |
| } |
| catch (IOException ioe) |
| { |
| |
| if (SanityManager.DEBUG) |
| { |
| if (pageData == null) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "caught an IOException in restoreRecordFromSlot " + |
| (PageKey)getIdentity() + " slot " + slot + |
| ", pageData is null"); |
| } |
| else |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "caught an IOException in reestoreRecordFromSlot, " + |
| (PageKey)getIdentity() + " slot " + slot + |
| ", pageData.length = " + |
| pageData.length + " pageSize = " + getPageSize()); |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "Hex dump of pageData \n " + |
| "--------------------------------------------------\n" + |
| pagedataToHexDump(pageData) + |
| "--------------------------------------------------\n"); |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "Attempt to dump page " + this.toString()); |
| } |
| } |
| |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| private StoredRecordHeader restoreLongRecordFromSlot( |
| Object[] row, |
| FetchDescriptor fetchDesc, |
| RecordHandle recordToLock, |
| StoredRecordHeader parent_recordHeader) |
| throws StandardException |
| { |
| |
| int slot = |
| findRecordById( |
| parent_recordHeader.getOverflowId(), Page.FIRST_SLOT_NUMBER); |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| try |
| { |
| int offset_to_row_data = |
| getRecordOffset(slot) + recordHeader.size(); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (getRecordOffset(slot) < |
| (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE)) |
| { |
| SanityManager.THROWASSERT( |
| "Incorrect offset. offset = " + |
| getRecordOffset(slot) + |
| ", offset should be < " + |
| "(PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) = " + |
| (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) + |
| ", current slot = " + slot + |
| ", total slotsInUse = " + slotsInUse); |
| } |
| } |
| |
| // position the array reading stream at beginning of row data |
| // just past the record header. |
| ArrayInputStream lrdi = rawDataIn; |
| lrdi.setPosition(offset_to_row_data); |
| |
| if (fetchDesc != null) |
| { |
| if (fetchDesc.getQualifierList() != null) |
| { |
| fetchDesc.reset(); |
| } |
| |
| readRecordFromArray( |
| row, |
| (fetchDesc.getValidColumns() == null) ? |
| row.length - 1 : fetchDesc.getMaxFetchColumnId(), |
| fetchDesc.getValidColumnsArray(), |
| fetchDesc.getMaterializedColumns(), |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| else |
| { |
| readRecordFromArray( |
| row, |
| row.length - 1, |
| (int[]) null, |
| (int[]) null, |
| lrdi, |
| recordHeader, |
| recordToLock); |
| } |
| |
| return(recordHeader.hasOverflow() ? recordHeader : null); |
| } |
| catch (IOException ioe) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (pageData == null) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "caught an IOException in restoreRecordFromSlot " + |
| (PageKey)getIdentity() + " slot " + slot + |
| ", pageData is null"); |
| } |
| else |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "caught an IOException in reestoreRecordFromSlot, " + |
| (PageKey)getIdentity() + " slot " + slot + |
| ", pageData.length = " + |
| pageData.length + " pageSize = " + getPageSize()); |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "Hex dump of pageData \n " + |
| "--------------------------------------------------\n" + |
| pagedataToHexDump(pageData) + |
| "--------------------------------------------------\n"); |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "Attempt to dump page " + this.toString()); |
| } |
| } |
| |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| /** |
| * Create a new record handle. |
| * <p> |
| * Return the next record id for allocation. Callers of this interface |
| * expect the next id to get bumped some where else - probably by |
| * storeRecordForInsert(). |
| * <p> |
| * |
| * @return The next id to assing to a row. |
| **/ |
| public int newRecordId() |
| { |
| return nextId; |
| } |
| |
| /** |
| * Create a new record handle, and bump the id. |
| * <p> |
| * Create a new record handle, and bump the id while holding the latch |
| * so that no other user can ever see this record id. This will lead |
| * to unused record id's in the case where an insert fails because there |
| * is not enough space on the page. |
| * <p> |
| * |
| * @return The next id to assing to a row. |
| **/ |
| public int newRecordIdAndBump() |
| { |
| // headerOutOfDate must be bumped as nextId is changing, and must |
| // eventually be updated in the page array. |
| headerOutOfDate = true; |
| |
| return nextId++; |
| } |
| |
| |
| /** |
| * Create a new record id based on current one passed in. |
| * <p> |
| * This interface is used for the "copy" insert interface of raw store |
| * where multiple rows are inserted into a page in a single logged |
| * operation. We don't want to bump the id until the operation is logged |
| * so we just allocated each id in order and then bump the next id at |
| * the end of the operation. |
| * <p> |
| * |
| * @return the next id based on the input id. |
| * |
| * @param recordId The id caller just used, return the next one. |
| * |
| **/ |
| protected int newRecordId(int recordId) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| recordId >= nextId, |
| "should not create a record Id that is already given out"); |
| } |
| |
| return recordId + 1; |
| } |
| |
| public boolean isOverflowPage() |
| { |
| return isOverflowPage; |
| } |
| |
| |
| |
| /************************************************************************** |
| * Public Methods specific to StoredPage: |
| ************************************************************************** |
| */ |
| |
| /** |
| * Get the full size of the page. |
| **/ |
| public final int getPageSize() |
| { |
| return pageData.length; |
| } |
| |
| |
| /** |
| * Zero out a portion of the page. |
| * |
| * @param offset position of first byte to clear |
| * @param length how many bytes to clear |
| **/ |
| protected final void clearSection(int offset, int length) |
| { |
| Arrays.fill(pageData, offset, offset + length, (byte) 0); |
| } |
| |
| /** |
| * The maximum free space on this page possible. |
| * <p> |
| * The the maximum amount of space that can be used on the page |
| * for the records and the slot offset table. |
| * NOTE: subclass may have overwitten it to report less freeSpace |
| * |
| * @return the maximum free space on this page possible. |
| * |
| **/ |
| protected int getMaxFreeSpace() |
| { |
| return getPageSize() - RECORD_SPACE_OFFSET - CHECKSUM_SIZE; |
| } |
| |
| /** |
| * The current free space on the page. |
| **/ |
| protected int getCurrentFreeSpace() |
| { |
| return freeSpace; |
| } |
| |
| /************************************************************************** |
| * Page header routines |
| ************************************************************************** |
| */ |
| |
| /** |
| * Read the page header from the page array. |
| * <p> |
| * Read the page header from byte form in the page array into in memory |
| * variables. |
| **/ |
| private void readPageHeader() |
| throws IOException |
| { |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| lrdi.setPosition(PAGE_HEADER_OFFSET); |
| long spare; |
| |
| isOverflowPage = lrdi.readBoolean(); |
| setPageStatus (lrdi.readByte()); |
| setPageVersion (lrdi.readLong()); |
| slotsInUse = lrdi.readUnsignedShort(); |
| nextId = lrdi.readInt(); |
| generation = lrdi.readInt(); // page generation (Future Use) |
| prevGeneration = lrdi.readInt(); // previous generation (Future Use) |
| bipLocation = lrdi.readLong(); // BIPage location (Future Use) |
| |
| // number of deleted rows on page, we start to store this release 2.0. |
| // for upgrade reasons, a 0 on disk means -1, so, we subtract one here. |
| deletedRowCount = lrdi.readUnsignedShort() - 1; |
| |
| // the next 4 (total 22 bytes) are reserved for future |
| spare = lrdi.readUnsignedShort(); |
| spare = lrdi.readInt(); // used by encryption |
| spare = lrdi.readLong(); |
| spare = lrdi.readLong(); |
| } |
| |
| |
| /** |
| * Update the page header in the page array. |
| * <p> |
| * Write the bytes of the page header, taking the values from those |
| * in the in memory variables. |
| **/ |
| private void updatePageHeader() |
| throws IOException |
| { |
| rawDataOut.setPosition(PAGE_HEADER_OFFSET); |
| |
| logicalDataOut.writeBoolean(isOverflowPage); |
| logicalDataOut.writeByte(getPageStatus()); |
| logicalDataOut.writeLong(getPageVersion()); |
| logicalDataOut.writeShort(slotsInUse); |
| logicalDataOut.writeInt(nextId); |
| logicalDataOut.writeInt(generation); // page generation (Future Use) |
| logicalDataOut.writeInt(prevGeneration); // previous generation (Future Use) |
| logicalDataOut.writeLong(bipLocation); // BIPage location (Future Use) |
| |
| // number of deleted rows on page, we start to store this release 2.0. |
| // for upgrade reasons, a 0 on disk means -1, so, we add one when we |
| // write it to disk. |
| logicalDataOut.writeShort(deletedRowCount + 1); |
| |
| logicalDataOut.writeShort(0); // reserved for future |
| logicalDataOut.writeInt( |
| dataFactory.random()); // random bytes for encryption |
| logicalDataOut.writeLong(0); // reserved for future |
| logicalDataOut.writeLong(0); // reserved for future |
| |
| // we put a random value int into the page if the database is encrypted |
| // so that the checksum will be very different even with the same |
| // page image, when we encrypt or decrypt the page, we move the |
| // checksum to the front so that the encrypted page will look very |
| // different even with just the one int difference. We never look at |
| // the value of the random number and we could have put it anywhere in |
| // the page as long as it doesn't obscure real data. |
| |
| headerOutOfDate = false; |
| } |
| |
| /** |
| * Update the page version number in the byte array |
| **/ |
| private void updatePageVersion() |
| throws IOException |
| { |
| rawDataOut.setPosition(PAGE_VERSION_OFFSET); |
| logicalDataOut.writeLong(getPageVersion()); |
| } |
| |
| /************************************************************************** |
| * Slot Offset & Length table manipulation |
| ************************************************************************** |
| */ |
| |
| /** |
| * Get the page offset of a given slot entry. |
| * <p> |
| * Get the page offset of a slot entry, this is not the offset of |
| * the record stored in the slot, but the offset of the actual slot. |
| * |
| * @return The page offset of a given slot entry. |
| * |
| * @param slot The array entry of the slot to find. |
| **/ |
| private int getSlotOffset(int slot) |
| { |
| // slot table grows backward from the spot at the end of the page just |
| // before the checksum which is located in the last 8 bytes of the page. |
| |
| return(slotTableOffsetToFirstEntry - (slot * slotEntrySize)); |
| } |
| |
| /** |
| * Get the page offset of the record associated with the input slot. |
| * <p> |
| * This is the actual offset on the page of the beginning of the record. |
| * |
| * @return The page offset of the record associated with the input slot. |
| * |
| * @param slot The array entry of the slot to find. |
| **/ |
| private int getRecordOffset(int slot) |
| { |
| byte[] data = pageData; |
| int offset = slotTableOffsetToFirstEntry - (slot * slotEntrySize); |
| |
| // offset on the page of the record is stored in the first 2 or 4 bytes |
| // of the slot table entry. Code has been inlined for performance |
| // critical low level routine. |
| // |
| // return( |
| // (slotFieldSize == SMALL_SLOT_SIZE) ? |
| // readUnsignedShort() : readInt()); |
| |
| return( |
| (slotFieldSize == SMALL_SLOT_SIZE) ? |
| |
| ((data[offset++] & 0xff) << 8) | |
| (data[offset] & 0xff) : |
| |
| (((data[offset++] & 0xff) << 24) | |
| ((data[offset++] & 0xff) << 16) | |
| ((data[offset++] & 0xff) << 8) | |
| ((data[offset] & 0xff) ))); |
| } |
| |
| /** |
| * Set the page offset of the record associated with the input slot. |
| * <p> |
| * This is the actual offset on the page of the beginning of the record. |
| * |
| * @param slot The array entry of the slot to set. |
| * @param recordOffset the new offset to set. |
| **/ |
| private void setRecordOffset(int slot, int recordOffset) |
| throws IOException |
| { |
| rawDataOut.setPosition(getSlotOffset(slot)); |
| |
| if (slotFieldSize == SMALL_SLOT_SIZE) |
| logicalDataOut.writeShort(recordOffset); |
| else |
| logicalDataOut.writeInt(recordOffset); |
| } |
| |
| /** |
| * Return length of row on this page. |
| * <p> |
| * Return the total length of data and header stored on this page for |
| * this record. This length is stored as the second "field" of the |
| * slot table entry. |
| * |
| * @return The length of the row on this page. |
| * |
| * @param slot the slot of the row to look up the length of. |
| * |
| **/ |
| protected int getRecordPortionLength(int slot) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (getRecordOffset(slot) <= 0) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "getRecordPortionLength failed with getRecordOffset(" + |
| slot + ") = " + |
| getRecordOffset(slot) + " must be greater than 0." + |
| "page dump = \n" + |
| toUncheckedString()); |
| SanityManager.THROWASSERT( |
| "bad record offset found in getRecordPortionLength()"); |
| } |
| } |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| lrdi.setPosition( |
| slotTableOffsetToFirstRecordLengthField - (slot * slotEntrySize)); |
| |
| return( |
| (slotFieldSize == SMALL_SLOT_SIZE) ? |
| lrdi.readUnsignedShort() : lrdi.readInt()); |
| } |
| |
| /** |
| * Return reserved length of row on this page. |
| * <p> |
| * Return the reserved length of this record. |
| * This length is stored as the third "field" of the slot table entry. |
| * |
| * @return The reserved length of the row on this page. |
| * |
| * @param slot the slot of the row to look up the length of. |
| * |
| **/ |
| public int getReservedCount(int slot) throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (getRecordOffset(slot) <= 0) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "getReservedCount failed with getRecordOffset(" + |
| slot + ") = " + |
| getRecordOffset(slot) + " must be greater than 0." + |
| "page dump = \n" + |
| toUncheckedString()); |
| SanityManager.THROWASSERT( |
| "bad record offset found in getReservedCount"); |
| } |
| } |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| lrdi.setPosition( |
| slotTableOffsetToFirstReservedSpaceField - (slot * slotEntrySize)); |
| |
| return( |
| (slotFieldSize == SMALL_SLOT_SIZE) ? |
| lrdi.readUnsignedShort() : lrdi.readInt()); |
| } |
| |
| |
| /** |
| Update the length of data stored on this page for this record |
| */ |
| /** |
| * Update the length of data stored on this page for this record |
| * <p> |
| * Update both the record length "field" and the reserved space "field" |
| * of the slot table entry associated with "slot". This length is stored |
| * as the second "field" of the slot table entry. The changes to these |
| * 2 fields are represented as the delta to apply to each field as input |
| * in "delta" and "reservedDelta." |
| * <p> |
| * |
| * @param slot the slot of the record to set. |
| * @param delta The amount the record length changed. |
| * @param reservedDelta The amount the reserved length changed. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void updateRecordPortionLength( |
| int slot, |
| int delta, |
| int reservedDelta) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(getRecordOffset(slot) != 0); |
| |
| if ((delta + reservedDelta) < 0) |
| SanityManager.THROWASSERT( |
| "total space of record is not allowed to shrink, delta == " |
| + delta + " reservedDelta = " + reservedDelta); |
| |
| if ((getRecordPortionLength(slot) + delta) < 0) |
| SanityManager.THROWASSERT( |
| "record portion length cannot be < 0.recordPortionLength = " |
| + getRecordPortionLength(slot) + " delta = " + delta); |
| |
| if ((getReservedCount(slot) + reservedDelta) < 0) |
| SanityManager.THROWASSERT( |
| "reserved space for record cannot be < 0. reservedCount = " |
| + getReservedCount(slot) + " reservedDelta = " |
| + reservedDelta); |
| } |
| |
| // position the stream to beginning of 2nd field of slot entry. |
| rawDataOut.setPosition( |
| slotTableOffsetToFirstRecordLengthField - (slot * slotEntrySize)); |
| |
| // write the new record length to 2nd field |
| if (slotFieldSize == SMALL_SLOT_SIZE) |
| logicalDataOut.writeShort(getRecordPortionLength(slot) + delta); |
| else |
| logicalDataOut.writeInt(getRecordPortionLength(slot) + delta); |
| |
| // if necessary, write the 3rd field - above write has positioned the |
| // stream to the 3rd field. |
| if (reservedDelta != 0) |
| { |
| if (slotFieldSize == SMALL_SLOT_SIZE) |
| { |
| logicalDataOut.writeShort( |
| getReservedCount(slot) + reservedDelta); |
| } |
| else |
| { |
| logicalDataOut.writeInt( |
| getReservedCount(slot) + reservedDelta); |
| } |
| } |
| } |
| |
| /** |
| * Initialize the in-memory slot table. |
| * <p> |
| * Initialize the in-memory slot table, ie. that of our super-class |
| * BasePage. Go through all the records on the page and set the |
| * freeSpace and firstFreeByte on page. |
| * <p> |
| * @param newIdentity The identity of the page we are trying to |
| * initialize, since we are in the middle of trying |
| * to build the page existing info in the class is |
| * not set up yet (like getIdentity()). |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void initSlotTable( |
| PageKey newIdentity) |
| throws StandardException |
| { |
| int localSlotsInUse = slotsInUse; |
| |
| // must initialize the header now |
| initializeHeaders(localSlotsInUse); |
| |
| // mark all the space on the page as free |
| clearAllSpace(); |
| |
| // first count the space occupied by the slot table |
| freeSpace -= localSlotsInUse * slotEntrySize; |
| |
| int lastSlotOnPage = -1; |
| int lastRecordOffset = -1; |
| |
| try |
| { |
| for (int slot = 0; slot < localSlotsInUse; slot++) |
| { |
| if (SanityManager.DEBUG) |
| { |
| int total_space = getTotalSpace(slot); |
| |
| if ((!isOverflowPage() && |
| (minimumRecordSize > total_space)) || |
| (isOverflowPage() && |
| (StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE > |
| total_space))) |
| { |
| // head rows including reserved space must be larger |
| // than minimumRecordSize. |
| // |
| // Overflow rows including reserved space must be |
| // larger than MAX_OVERFLOW_ONLY_REC_SIZE. |
| |
| SanityManager.THROWASSERT( |
| "initSlotTable consistency check failed: " + |
| " slot " + slot + |
| " minimumRecordSize = " + minimumRecordSize + |
| " totalSpace = " + total_space + |
| " recordPortionLength = " + |
| getRecordPortionLength(slot) + |
| " reservedCount = " + getReservedCount(slot)); |
| } |
| } |
| |
| int recordOffset = getRecordOffset(slot); |
| |
| // check that offset points into the record space area. |
| if ((recordOffset < RECORD_SPACE_OFFSET) || |
| (recordOffset >= (getPageSize() - CHECKSUM_SIZE))) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, newIdentity)); |
| } |
| |
| if (recordOffset > lastRecordOffset) |
| { |
| lastRecordOffset = recordOffset; |
| lastSlotOnPage = slot; |
| } |
| } |
| |
| bumpRecordCount(localSlotsInUse); |
| |
| if (lastSlotOnPage != -1) |
| { |
| // Calculate the firstFreeByte for the page, |
| // and the freeSpace on Page |
| |
| firstFreeByte = |
| lastRecordOffset + getTotalSpace(lastSlotOnPage); |
| freeSpace -= firstFreeByte - RECORD_SPACE_OFFSET; |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| if ((freeSpace < 0) || |
| (firstFreeByte > getSlotOffset(slotsInUse - 1)) || |
| ((firstFreeByte + freeSpace) != |
| getSlotOffset(slotsInUse - 1))) |
| { |
| SanityManager.THROWASSERT( |
| "firstFreeByte = " + firstFreeByte |
| + ", freeSpace = " + freeSpace |
| + ", slotOffset = " + (getSlotOffset(slotsInUse - 1)) |
| + ", slotsInUse = " + localSlotsInUse); |
| } |
| |
| if (localSlotsInUse == 0) |
| { |
| SanityManager.ASSERT( |
| firstFreeByte == |
| (getPageSize() - totalSpace - CHECKSUM_SIZE)); |
| } |
| } |
| |
| // upgrade issue. Pre 1.5 release, we do not store deletedRowCount |
| // therefore, if we are accessing an older database, |
| // we need to calculate the deletedRowCount here. |
| if (deletedRowCount == -1) |
| { |
| int count = 0; |
| int maxSlot = slotsInUse; |
| for (int slot = FIRST_SLOT_NUMBER ; slot < maxSlot; slot++) |
| { |
| if (isDeletedOnPage(slot)) |
| count++; |
| } |
| deletedRowCount = count; |
| } |
| |
| } |
| catch (IOException ioe) |
| { |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, newIdentity)); |
| } |
| } |
| |
| |
| /** |
| * Set up a new slot entry. |
| * <p> |
| * |
| * @param slot the slot to initialize. |
| * @param recordOffset the offset on the page to find the record. |
| * @param recordPortionLength the actual length of record+hdr on page. |
| * @param reservedSpace the reserved length associated with record. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void setSlotEntry( |
| int slot, |
| int recordOffset, |
| int recordPortionLength, |
| int reservedSpace) |
| throws IOException |
| { |
| rawDataOut.setPosition(getSlotOffset(slot)); |
| |
| if (SanityManager.DEBUG) |
| { |
| if ((recordPortionLength < 0) || |
| (reservedSpace < 0) || |
| (recordPortionLength >= getPageSize()) || |
| (reservedSpace >= getPageSize())) |
| { |
| SanityManager.THROWASSERT( |
| "recordPortionLength and reservedSpace must " + |
| "be > 0, and < page size." |
| + " slot = " + slot |
| + ", in use = " + slotsInUse |
| + ", recordOffset = " + recordOffset |
| + ", recordPortionLength = " + recordPortionLength |
| + ", reservedSpace = " + reservedSpace); |
| } |
| |
| if (recordOffset < (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE)) |
| { |
| SanityManager.THROWASSERT( |
| "Record offset must be after the page header." |
| + " slot = " + slot |
| + ", in use = " + slotsInUse |
| + ", recordOffset = " + recordOffset |
| + ", recordPortionLength = " + recordPortionLength |
| + ", reservedSpace = " + reservedSpace); |
| } |
| } |
| |
| if (slotFieldSize == SMALL_SLOT_SIZE) |
| { |
| logicalDataOut.writeShort(recordOffset); |
| logicalDataOut.writeShort(recordPortionLength); |
| logicalDataOut.writeShort(reservedSpace); |
| } |
| else |
| { |
| logicalDataOut.writeInt(recordOffset); |
| logicalDataOut.writeInt(recordPortionLength); |
| logicalDataOut.writeInt(reservedSpace); |
| } |
| } |
| |
| /** |
| * Insert a new slot entry into the current slot array. |
| * <p> |
| * Shift the existing slots from slot to (slotsInUse - 1) up by one. |
| * Up here means from low slot to high slot (e.g from slot 2 to slot 3). |
| * Our slot table grows backward so we have to be careful here. |
| * |
| * @param slot Position the new slot will take |
| * @param recordOffset Offset of the record for the new slot |
| * @param recordPortionLength Length of the record stored in the new slot |
| * @param reservedSpace Length of reserved space of record in slot |
| * |
| **/ |
| private void addSlotEntry( |
| int slot, |
| int recordOffset, |
| int recordPortionLength, |
| int reservedSpace) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if ((slot < 0) || (slot > slotsInUse)) |
| SanityManager.THROWASSERT("invalid slot " + slot); |
| |
| if ((recordPortionLength < 0) || (reservedSpace < 0)) |
| SanityManager.THROWASSERT( |
| "recordPortionLength and reservedSpace must be > 0." + |
| "recordPortionLength = " + recordPortionLength + |
| " reservedSpace = " + reservedSpace); |
| |
| if (recordOffset < (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE)) |
| { |
| SanityManager.THROWASSERT( |
| "Record offset must be after the page header." |
| + " slot = " + slot |
| + ", in use = " + slotsInUse |
| + ", recordOffset = " + recordOffset |
| + ", recordPortionLength = " + recordPortionLength |
| + ", reservedSpace = " + reservedSpace); |
| } |
| } |
| |
| int newSlotOffset; |
| |
| // TODO - (mikem) - I think the math below could be slightly optimized. |
| |
| if (slot < slotsInUse) |
| { |
| // inserting a slot into the middle of array so shift all the |
| // slots from "slot" logically up by one |
| |
| int startOffset = |
| getSlotOffset(slotsInUse - 1); |
| |
| int length = |
| (getSlotOffset(slot) + slotEntrySize) - startOffset; |
| |
| newSlotOffset = getSlotOffset(slotsInUse); |
| |
| System.arraycopy( |
| pageData, startOffset, pageData, newSlotOffset, length); |
| } |
| else |
| { |
| // We are adding at end of slot table, so no moving necessary. |
| newSlotOffset = getSlotOffset(slot); |
| } |
| |
| freeSpace -= slotEntrySize; |
| |
| slotsInUse++; |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty may be called unlatched |
| |
| setSlotEntry(slot, recordOffset, recordPortionLength, reservedSpace); |
| } |
| |
| /** |
| * Remove slot entry from slot array. |
| * <p> |
| * Remove a storage slot at slot. Shift the existing slots from |
| * slot+1 to (slotsInUse - 1) down by one.. |
| * Down here means from high slot to low slot (e.g from slot 3 to slot 2) |
| * |
| * @param slot The slot to delete. |
| * |
| **/ |
| private void removeSlotEntry(int slot) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if ((slot < 0) || (slot >= slotsInUse)) |
| SanityManager.THROWASSERT("invalid slot " + slot); |
| } |
| |
| int oldEndOffset = getSlotOffset(slotsInUse - 1); |
| int newEndOffset = getSlotOffset(slotsInUse - 2); |
| |
| if (slot != slotsInUse - 1) |
| { |
| // if not removing the last slot, need to shift |
| |
| // now shift all the slots logically down by one |
| // from (slot+1 to slotsInUse-1) to (slot and slotsInUse-2) |
| int length = getSlotOffset(slot) - oldEndOffset; |
| |
| System.arraycopy( |
| pageData, oldEndOffset, pageData, newEndOffset, length); |
| } |
| |
| // clear out the last slot |
| clearSection(oldEndOffset, slotEntrySize); |
| |
| // mark the space as free after we have removed the slot |
| // no need to keep the space reserved for rollback as this is only |
| // called for purge. |
| freeSpace += slotEntrySize; |
| |
| slotsInUse--; |
| |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty maybe called unlatched |
| } |
| |
| /** |
| * create the record header for the specific slot. |
| * <p> |
| * Create a new record header object, initialize it, and add it |
| * to the array of cache'd record headers on this page. Finally return |
| * reference to the initialized record header. |
| * |
| * @return The record header for the specific slot. |
| * |
| * @param slot return record header of this slot. |
| **/ |
| public StoredRecordHeader recordHeaderOnDemand(int slot) |
| { |
| StoredRecordHeader recordHeader = |
| new StoredRecordHeader(pageData, getRecordOffset(slot)); |
| |
| setHeaderAtSlot(slot, recordHeader); |
| |
| return recordHeader; |
| } |
| |
| /************************************************************************** |
| * Record based routines. |
| ************************************************************************** |
| */ |
| |
| /** |
| * Is entire record on the page? |
| * <p> |
| * |
| * @return true if the entire record at slot is on this page, |
| * i.e, no overflow row or long columns. |
| * |
| * @param slot Check record at this slot. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public boolean entireRecordOnPage(int slot) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched()); |
| } |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| if (recordHeader.hasOverflow()) |
| return false; |
| |
| // the row chain does not overflow, we need to walk all the fields to |
| // make sure they are not long columns. |
| |
| try |
| { |
| |
| int offset = getRecordOffset(slot); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (offset < (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE)) |
| { |
| SanityManager.THROWASSERT( |
| "Incorrect offset. offset = " + offset + |
| ", offset should be < " + |
| "(PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) = " + |
| (PAGE_HEADER_OFFSET + PAGE_HEADER_SIZE) + |
| ", current slot = " + slot + |
| ", total slotsInUse = " + slotsInUse); |
| } |
| |
| if (recordHeader.getFirstField() != 0) |
| { |
| SanityManager.THROWASSERT( |
| "Head row piece should start at field 0 but is not," + |
| ", current slot = " + slot + |
| ", total slotsInUse = " + slotsInUse + |
| "page = " + this); |
| } |
| } |
| |
| int numberFields = recordHeader.getNumberFields(); |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // position after the record header, at 1st column. |
| lrdi.setPosition(offset + recordHeader.size()); |
| |
| for (int i = 0; i < numberFields; i++) |
| { |
| int fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| if (StoredFieldHeader.isOverflow(fieldStatus)) |
| return false; |
| |
| int fieldLength = |
| StoredFieldHeader.readFieldDataLength( |
| lrdi, fieldStatus, slotFieldSize); |
| |
| if (fieldLength != 0) |
| lrdi.setPosition(lrdi.getPosition() + fieldLength); |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| |
| // we have examined all the fields on this page and none overflows |
| return true; |
| } |
| |
| /** |
| * Purge one row on an overflow page. |
| * <p> |
| * HeadRowHandle is the recordHandle pointing to the head row piece. |
| * <p> |
| * |
| * @param slot slot number of row to purge. |
| * @param headRowHandle recordHandle of the head row piece. |
| * @param needDataLogged when true data is logged for purges otherwise just headers. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void purgeOverflowAtSlot( |
| int slot, |
| RecordHandle headRowHandle, |
| boolean needDataLogged) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched()); |
| SanityManager.ASSERT(isOverflowPage()); |
| } |
| |
| if ((slot < 0) || (slot >= slotsInUse)) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SLOT_NOT_ON_PAGE); |
| } |
| |
| // TODO (mikem) - should a global scratch variable be used? |
| |
| // this is an overflow page purge, no need to lock the head row (it |
| // has already been locked, hopefully). No need to check for long rows |
| // (they have already been deleted, hopefully). |
| RawTransaction t = owner.getTransaction(); |
| int[] recordId = new int[1]; |
| |
| recordId[0] = getHeaderAtSlot(slot).getId(); |
| |
| owner.getActionSet().actionPurge(t, this, slot, 1, recordId, needDataLogged); |
| } |
| |
| /** |
| * Purge the column chain that starts at overflowPageId, overflowRecordId |
| * <p> |
| * Purge just the column chain that starts at the input address. |
| * The long column chain is pointed at by a field in a row. The long |
| * column is then chained as a sequence of "rows", the last column then |
| * points to the next segment of the chain on each page. |
| * Long columns chains currently are only one row per page so the next |
| * slot of a row in a long row chain should always be the first slot. |
| * <p> |
| * |
| * @param overflowPageId The page where the long column chain starts. |
| * @param overflowRecordId The record id where long column chain starts. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void purgeOneColumnChain( |
| long overflowPageId, |
| int overflowRecordId) |
| throws StandardException |
| { |
| StoredPage pageOnColumnChain = null; |
| boolean removePageHappened = false; |
| |
| try |
| { |
| while (overflowPageId != ContainerHandle.INVALID_PAGE_NUMBER) |
| { |
| |
| // Now loop over the column chain and get all the column pieces. |
| pageOnColumnChain = getOverflowPage(overflowPageId); |
| removePageHappened = false; |
| |
| if (pageOnColumnChain == null) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT( |
| "got null page following long column chain. " + |
| "Head column piece at " + getIdentity() + |
| " null page at " + overflowPageId); |
| |
| break; // Don't know what to do here, the column chain |
| // is broken. Don't bomb, go to the next field. |
| } |
| |
| int overflowSlotId = FIRST_SLOT_NUMBER; |
| if (SanityManager.DEBUG) |
| { |
| int checkSlot = |
| pageOnColumnChain.findRecordById( |
| overflowRecordId, FIRST_SLOT_NUMBER); |
| |
| if (overflowSlotId != checkSlot) |
| { |
| SanityManager.THROWASSERT( |
| "Long column is not at the expected " + |
| FIRST_SLOT_NUMBER + " slot, instead at slot " + |
| checkSlot); |
| } |
| |
| SanityManager.ASSERT(pageOnColumnChain.recordCount() == 1, |
| "long column page has > 1 record"); |
| } |
| |
| // Hold on to the pointer to next page on the chain before |
| // we remove the long column page. |
| RecordHandle nextColumnPiece = |
| pageOnColumnChain.getNextColumnPiece(overflowSlotId); |
| |
| if (pageOnColumnChain.recordCount() == 1) |
| { |
| removePageHappened = true; |
| owner.removePage(pageOnColumnChain); |
| } |
| else |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT( |
| "page on column chain has more then one record" + |
| pageOnColumnChain.toString()); |
| |
| pageOnColumnChain.unlatch(); |
| pageOnColumnChain = null; |
| } |
| |
| // Chase the column chain pointer. |
| if (nextColumnPiece != null) |
| { |
| overflowPageId = nextColumnPiece.getPageNumber(); |
| overflowRecordId = nextColumnPiece.getId(); |
| } |
| else |
| { |
| // terminate the loop |
| overflowPageId = ContainerHandle.INVALID_PAGE_NUMBER; |
| } |
| } |
| } |
| finally |
| { |
| // if we raised an exception before the page is removed, make sure |
| // we unlatch the page |
| |
| if (!removePageHappened && pageOnColumnChain != null) |
| { |
| pageOnColumnChain.unlatch(); |
| pageOnColumnChain = null; |
| } |
| } |
| } |
| |
| /** |
| * purge long columns chains which eminate from this page. |
| * <p> |
| * Purge all the long column chains emanating from the record on this slot |
| * of this page. The headRowHandle is the record handle of the head row |
| * piece of this row - if this page is the head row, then headRowHandle is |
| * the record handle at the slot. Otherwise, headRowHandle points to a |
| * row on a different page, i.e., the head page. |
| * <p> |
| * |
| * @param t The raw transaction doing the purging. |
| * @param slot The slot of the row to purge. |
| * @param headRowHandle The RecordHandle of the head row. |
| * |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void purgeColumnChains( |
| RawTransaction t, |
| int slot, |
| RecordHandle headRowHandle) |
| throws StandardException |
| { |
| try |
| { |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| int numberFields = recordHeader.getNumberFields(); |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // position the stream to just after record header. |
| int offset = getRecordOffset(slot) + recordHeader.size(); |
| lrdi.setPosition(offset); |
| |
| for (int i = 0; i < numberFields; i++) |
| { |
| int fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| int fieldLength = |
| StoredFieldHeader.readFieldDataLength( |
| lrdi, fieldStatus, slotFieldSize); |
| |
| if (!StoredFieldHeader.isOverflow(fieldStatus)) |
| { |
| // skip this field, it is not an long column |
| if (fieldLength != 0) |
| lrdi.setPosition(lrdi.getPosition() + fieldLength); |
| continue; |
| } |
| else |
| { |
| |
| // Got an overflow field. The column value is the |
| // <pageId, recordId> pair where the next column piece is |
| // residing |
| |
| long overflowPageId = |
| CompressedNumber.readLong((InputStream)lrdi); |
| int overflowRecordId = |
| CompressedNumber.readInt((InputStream)lrdi); |
| |
| purgeOneColumnChain(overflowPageId, overflowRecordId); |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| /** |
| * Purge all the overflow columns and overflow rows of the record at slot. |
| * <p> |
| * Purge all the overflow columns and overflow rows of the record at slot. |
| * This is called by BasePage.purgeAtSlot, the head row piece is purged |
| * there. |
| * <p> |
| * |
| * @param t The raw transaction doing the purging. |
| * @param slot The slot of the row to purge. |
| * @param headRowHandle The RecordHandle of the head row. |
| * @param needDataLogged when true data is logged for purges otherwise just headers. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void purgeRowPieces( |
| RawTransaction t, |
| int slot, |
| RecordHandle headRowHandle, |
| boolean needDataLogged) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isOverflowPage() == false, |
| "not expected to call purgeRowPieces on a overflow page"); |
| |
| // purge the long columns which start on this page. |
| purgeColumnChains(t, slot, headRowHandle); |
| |
| // drive this loop from the head page. Walk each "long" row piece in |
| // the row chain. |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| while (recordHeader.hasOverflow()) |
| { |
| |
| // nextPageInRowChain, is the page with the next row piece |
| StoredPage nextPageInRowChain = |
| getOverflowPage(recordHeader.getOverflowPage()); |
| |
| if (nextPageInRowChain == null) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.THROWASSERT( |
| "got null page following long row chain. " + |
| "Head row piece at " + getIdentity() + " slot " + |
| slot + " headRecord " + headRowHandle + |
| ". Broken row chain at " + |
| recordHeader.getOverflowPage() + ", " + |
| recordHeader.getOverflowId()); |
| |
| } |
| |
| break; // Don't know what to do here, the row chain is |
| // broken. Don't bomb, just return. |
| } |
| |
| try |
| { |
| |
| int nextPageSlot = |
| getOverflowSlot(nextPageInRowChain, recordHeader); |
| |
| // First get rid of all long columns from the next row piece. |
| nextPageInRowChain.purgeColumnChains( |
| t, nextPageSlot, headRowHandle); |
| |
| // Before we purge the next row piece, get the row header to |
| // see if we need to continue the loop. |
| recordHeader = nextPageInRowChain.getHeaderAtSlot(nextPageSlot); |
| |
| // Lastly, purge the next row piece. If the next row piece is |
| // the only thing in the entire page, just deallocate the page. |
| // We can do this because the page is deallocated in this |
| // transaction. If we defer this to post commit processing, |
| // then we have to first purge the row piece and also remember |
| // the page time stamp. |
| |
| if (nextPageSlot == 0 && nextPageInRowChain.recordCount() == 1) |
| { |
| // This is an overflow page and we just purged the last row. |
| // Free the page. Cannot do it in post commit because the |
| // head row is gone and cannot be locked at post commit to |
| // stablelize the row chain. |
| |
| try |
| { |
| owner.removePage(nextPageInRowChain); |
| } |
| finally |
| { |
| // Remove Page guarantees to unlatch the page even |
| // if an exception is thrown, need not unlatch it |
| // again. |
| nextPageInRowChain = null; |
| } |
| } |
| else |
| { |
| nextPageInRowChain.purgeOverflowAtSlot( |
| nextPageSlot, headRowHandle, needDataLogged); |
| |
| nextPageInRowChain.unlatch(); |
| nextPageInRowChain = null; |
| } |
| } |
| finally |
| { |
| // Unlatch the next row piece before getting the next page in |
| // the row chain. |
| if (nextPageInRowChain != null) |
| { |
| nextPageInRowChain.unlatch(); |
| nextPageInRowChain = null; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Remove a column chain that may have been orphaned by an update. |
| * <p> |
| * Remove a column chain that may have been orphaned by an update. This |
| * is executed as a post commit operation. This page is the head page of |
| * the row which used to point to the column chain in question. The |
| * location of the orphaned column chain is in the ReclaimSpace record. |
| * <BR> |
| * MT - latched. No lock will be gotten, the head record must already be |
| * locked exclusive with no outstanding changes that can be rolled back. |
| * <p> |
| * |
| * @param work object describing the chain to remove. |
| * @param containerHdl open container handle to use to remove chain. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| /* package */ |
| void removeOrphanedColumnChain( |
| ReclaimSpace work, |
| ContainerHandle containerHdl) |
| throws StandardException |
| { |
| // First we need to make sure that this is the first and only time |
| // this long column is begin reclaimed, to do this we get the first |
| // page on the long column chain and compare its page time stamp. |
| // If it is different, don't do anything. |
| // |
| // Next we need to make sure the update operation commits - we do |
| // this by finding the row headed by headRecord, go to the column |
| // in question and see if it points to the first page of the long |
| // column chain we want to reclaim. If it does then the update |
| // operation has rolled back and we don't want to reclaim it. |
| // |
| // After we do the above 2 checks, we can reclaim the column |
| // chain. |
| StoredPage headOfChain = |
| (StoredPage)containerHdl.getPageNoWait(work.getColumnPageId()); |
| |
| // If someone has it latched, not reclaimable |
| if (headOfChain == null) |
| return; |
| |
| // If the column has been touched, it is not orphaned. Not reclaimable. |
| boolean pageUnchanged = |
| headOfChain.equalTimeStamp(work.getPageTimeStamp()); |
| |
| headOfChain.unlatch(); // unlatch it for now. |
| |
| if (pageUnchanged == false) |
| return; |
| |
| // Now get to the column in question and make sure it is no longer |
| // pointing to the column chain. |
| |
| RecordHandle headRowHandle = work.getHeadRowHandle(); |
| |
| if (SanityManager.DEBUG) |
| { |
| // System.out.println("Executing in removeOrphanedColumnChain."); |
| // System.out.println("work = " + work); |
| // System.out.println("head = " + headOfChain); |
| // System.out.println("this = " + this); |
| |
| SanityManager.ASSERT(isLatched()); |
| SanityManager.ASSERT( |
| headRowHandle.getPageNumber() == getPageNumber(), |
| "got wrong head page"); |
| } |
| |
| // First get the row. |
| int slot = |
| findRecordById( |
| headRowHandle.getId(), headRowHandle.getSlotNumberHint()); |
| |
| // If slot < 0, it means the whole record is gone, the column chain is |
| // definitely orphaned. |
| |
| if (slot >= 0) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (isOverflowPage()) |
| { |
| SanityManager.THROWASSERT( |
| "Page " + getPageNumber() + " is overflow " + |
| "\nwork = " + work + |
| "\nhead = " + headOfChain + |
| "\nthis = " + this); |
| } |
| } |
| |
| // Find the page with the column in question on it. |
| StoredPage pageInRowChain = this; // Start with the head page. |
| |
| try |
| { |
| |
| int columnId = work.getColumnId(); |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(recordHeader.getFirstField() == 0, |
| "Head row piece should start at field 0 but is not"); |
| |
| // See if columnId is on pageInRowChain. |
| while ((recordHeader.getNumberFields() + |
| recordHeader.getFirstField()) <= columnId) |
| { |
| // The column in question is not on pageInRowChain. |
| |
| if (pageInRowChain != this) |
| { |
| // Keep the head page latched. |
| pageInRowChain.unlatch(); |
| pageInRowChain = null; |
| } |
| |
| if (recordHeader.hasOverflow()) |
| { |
| // Go to the next row piece |
| pageInRowChain = |
| getOverflowPage(recordHeader.getOverflowPage()); |
| recordHeader = |
| pageInRowChain.getHeaderAtSlot( |
| getOverflowSlot(pageInRowChain, recordHeader)); |
| } |
| else |
| { |
| // Don't know why, but this is the last column. |
| // Anyway, the column chain is definite orphaned. |
| // This can happen if the update, or subsequent |
| // updates, shrink the number of columns in the row. |
| break; |
| } |
| } |
| |
| if ((recordHeader.getNumberFields() + |
| recordHeader.getFirstField()) > columnId) |
| { |
| // RecordHeader is the record header of the row piece on |
| // pageInRowChain. The column in question exists and is in |
| // that row piece. |
| if (!pageInRowChain.isColumnOrphaned( |
| recordHeader, columnId, |
| work.getColumnPageId(), work.getColumnRecordId())) |
| { |
| // The column is not orphaned, row still points to it. |
| if (pageInRowChain != this) |
| { |
| // Keep the head page latched. |
| pageInRowChain.unlatch(); |
| pageInRowChain = null; |
| } |
| return; |
| } |
| } |
| |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| finally |
| { |
| if (pageInRowChain != this && pageInRowChain != null) |
| pageInRowChain.unlatch(); |
| } |
| } |
| |
| // If we get this far, we have verified that the column chain is indeed |
| // orphaned. Get rid of the column chain. |
| |
| long nextPageId = work.getColumnPageId(); |
| int nextRecordId = work.getColumnRecordId(); |
| |
| purgeOneColumnChain(nextPageId, nextRecordId); |
| } |
| |
| /** |
| * See if there is a orphaned long colum chain or not. |
| * <p> |
| * See if there is a orphaned long colum chain or not. This is a helper |
| * function for removeOrphanedChain. This page, which may be a head page |
| * or overflow page, contains the column specified in columnId. It used to |
| * point to a long column chain at oldPageId and oldRecordId. Returns true |
| * if it no longer points to that long column chain. |
| * <p> |
| * |
| * @return true if page no longer points to the long column chain. |
| * |
| * @param recordHeader record header which used to point at the long column |
| * @param columnId column id of the long column in head. |
| * @param oldPageId the page id where the long column used to be. |
| * @param oldRecordId the record id where the long column used to be. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private boolean isColumnOrphaned( |
| StoredRecordHeader recordHeader, |
| int columnId, |
| long oldPageId, |
| long oldRecordId) |
| throws StandardException, IOException |
| { |
| int slot = findRecordById(recordHeader.getId(), Page.FIRST_SLOT_NUMBER); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(slot >= 0, "overflow row chain truncated"); |
| |
| SanityManager.ASSERT( |
| columnId >= recordHeader.getFirstField(), |
| "first column on page > expected"); |
| } |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // set read position to data portion of record to check. |
| int offset = getRecordOffset(slot); |
| lrdi.setPosition(offset + recordHeader.size()); |
| |
| // skip until you get to the record in question. |
| for (int i = recordHeader.getFirstField(); i < columnId; i++) |
| skipField(lrdi); |
| |
| // read in the info of the column we are interested in. |
| int fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| int fieldLength = StoredFieldHeader.readFieldDataLength |
| (lrdi, fieldStatus, slotFieldSize); |
| |
| if (StoredFieldHeader.isOverflow(fieldStatus)) |
| { |
| // it is still an overflow field, check if it still points to |
| // overflow column in question. |
| |
| long ovflowPage = CompressedNumber.readLong((InputStream) lrdi); |
| int ovflowRid = CompressedNumber.readInt((InputStream) lrdi); |
| |
| if (ovflowPage == oldPageId && ovflowRid == oldRecordId) |
| { |
| // This field still points to the column chain, the |
| // update must have rolled back. |
| return false; |
| } |
| } |
| |
| // Else, either the field is no longer a long column, or it doesn't |
| // point to oldPageId, oldRecordId. The column chain is orphaned. |
| return true; |
| } |
| |
| /** |
| @return a recordHandle pointing to the next piece of the column chain. |
| This page must be an overflow page that is in a column chain. If this |
| is the last piece of the overflow colum, return null. |
| |
| @param slot the slot number where the current piece of overflow column |
| is at. |
| @exception StandardException Derby Standard Error Policy |
| */ |
| /** |
| * Return the next recordHandle in a long column chain. |
| * <p> |
| * Return a recordHandle pointing to the next piece of the column chain. |
| * This page must be an overflow page that is in a column chain. If this |
| * is the last piece of the overflow colum, return null. |
| * <p> |
| * |
| * @return The next record handle in a long column chain. |
| * |
| * @param slot The slot of the current long column piece. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private RecordHandle getNextColumnPiece(int slot) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched()); |
| SanityManager.ASSERT(isOverflowPage(), |
| "not expected to call getNextColumnPiece on non-overflow page"); |
| |
| if (recordCount() != 1) |
| { |
| SanityManager.THROWASSERT( |
| "getNextColumnPiece called on a page with " + |
| recordCount() + " rows"); |
| } |
| } |
| |
| try |
| { |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| int numberFields = |
| recordHeader.getNumberFields(); |
| |
| if (SanityManager.DEBUG) |
| { |
| if ((numberFields > 2) || (numberFields < 1)) |
| { |
| SanityManager.THROWASSERT( |
| "longColumn record header must have 1 or 2 fields." + |
| " numberFields = " + numberFields); |
| } |
| } |
| |
| if (numberFields != 2) // End of column chain. |
| return null; |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // The 2nd field is the pointer to the next page in column chain. |
| |
| int offset = getRecordOffset(slot) + recordHeader.size(); |
| lrdi.setPosition(offset); |
| |
| // skip the first field |
| skipField(lrdi); |
| |
| // the 2nd field should be <pageId, recordId> pair, return the |
| // pageId part and skip over the length. |
| int fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| int fieldLength = StoredFieldHeader.readFieldDataLength |
| (lrdi, fieldStatus, slotFieldSize); |
| |
| long ovflowPage = CompressedNumber.readLong((InputStream) lrdi); |
| int ovflowRid = CompressedNumber.readInt((InputStream) lrdi); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (!StoredFieldHeader.isOverflow(fieldStatus)) |
| { |
| // In version 1.5, the first field is overflow and the |
| // second is not. In version 2.0 onwards, the first field |
| // is not overflow and the second is overflow (the overflow |
| // bit goes with the overflow pointer). Check first field |
| // to make sure its overflow bit is set on. |
| // Offset still points to the first column. |
| lrdi.setPosition(offset); |
| fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| SanityManager.ASSERT( |
| StoredFieldHeader.isOverflow(fieldStatus)); |
| } |
| } |
| |
| // RESOLVE : this new can get expensive if the column chain is very |
| // long. The reason we do this is because we need to return the |
| // page number and the rid, if we assume that the long column is |
| // always at slot 0, we can return only the page. |
| |
| return owner.makeRecordHandle(ovflowPage, ovflowRid); |
| |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| |
| /************************************************************************** |
| * Page space usage |
| ************************************************************************** |
| */ |
| |
| /** |
| * initialize the in memory variables associated with space maintenance. |
| * <p> |
| * Get the total available space on an empty page. |
| * initSlotTable() must be called after the page has been read in. |
| **/ |
| private void initSpace() |
| { |
| // NOTE: subclass may have overwitten it to report less freeSpace, |
| // always call getMaxFreeSpace() to get total space. |
| totalSpace = getMaxFreeSpace(); |
| |
| // estimate RH will be about 16 bytes: |
| // (1 - status, 1 - id, 1 - #fields, 1 - 1stField, 12 - overflow ptr) |
| |
| // RESOLVED: track# 3370, 3368 |
| // In the old code below, spareSpace/100 is integer division. This means |
| // that you get a value of 0 for it as long as spareSpace is between 0 |
| // and 99. But if spareSpace is 100 you get a value of 1. This resulted |
| // in a negative value for maxFieldSize. This caused e.g. the isLong |
| // method to behave incorrectly when spareSpace is 100. |
| // |
| // RESOLVED: track# 4385 |
| // maxFieldSize is a worst case calculation for the size of a record |
| // on an empty page, with a single field, but still allow room for |
| // an overflow pointer if another field is to be added. If you don't |
| // account for the overflow pointer then you can get into the situation |
| // where the code puts the field on the page (not making it a long |
| // column), then runs out of space on next column but can't fit overflow |
| // pointer, so backs up and removes the column from page, and tries |
| // again on next overflow page - looping forever. |
| // |
| // maxFieldSize = |
| // totalSpace * (1 - spareSpace/100) - slotEntrySize |
| // - 16 - OVERFLOW_POINTER_SIZE; |
| |
| maxFieldSize = totalSpace - slotEntrySize - 16 - OVERFLOW_POINTER_SIZE; |
| |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(maxFieldSize >= 0); |
| // DERBY-3099: maxFieldSize was calculated before slotFieldSize and |
| // slotEntrySize had been initialized. |
| int expectedFieldSize = calculateSlotFieldSize(pageData.length); |
| SanityManager.ASSERT(slotFieldSize == expectedFieldSize, |
| "slotFieldSize uninitialized"); |
| SanityManager.ASSERT(slotEntrySize == 3 * expectedFieldSize, |
| "slotEntrySize uninitialized"); |
| } |
| } |
| |
| /** |
| * Initialize the freeSpace count and set the firstFreeByte on page |
| **/ |
| private void clearAllSpace() |
| { |
| freeSpace = totalSpace; |
| firstFreeByte = getPageSize() - totalSpace - CHECKSUM_SIZE; |
| } |
| |
| /** |
| * Compress out the space specified by startByte and endByte. |
| * <p> |
| * As part of moving rows, updating rows, purging rows compact the space |
| * left between rows. |
| * <p> |
| * |
| * @param startByte compress out space starting at startByte offset |
| * @param endByte compress out space ending at endByte offset |
| * |
| **/ |
| private void compressPage( |
| int startByte, |
| int endByte) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (((endByte + 1) > firstFreeByte) || (startByte > firstFreeByte)) |
| { |
| SanityManager.THROWASSERT( |
| "startByte = " + startByte + " endByte = " + endByte + |
| " firstFreeByte = " + firstFreeByte); |
| } |
| } |
| |
| int lengthToClear = endByte + 1 - startByte; |
| |
| // see if these were not the last occupied record space on the page |
| if ((endByte + 1) != firstFreeByte) |
| { |
| // Shift everything down the page. |
| int moveLength = (firstFreeByte - endByte - 1); |
| |
| System.arraycopy( |
| pageData, (endByte + 1), pageData, startByte, moveLength); |
| |
| // fix the page offsets of the rows further down the page |
| for (int slot = 0; slot < slotsInUse; slot++) |
| { |
| int offset = getRecordOffset(slot); |
| |
| if (offset >= (endByte + 1)) |
| { |
| offset -= lengthToClear; |
| setRecordOffset(slot, offset); |
| } |
| } |
| } |
| |
| freeSpace += lengthToClear; |
| firstFreeByte -= lengthToClear; |
| |
| clearSection(firstFreeByte, lengthToClear); |
| } |
| |
| /** |
| * Free up required bytes by shifting rows "down" the page. |
| * <p> |
| * Expand page, move all the data from start Offset down the page by |
| * the amount required to free up the required bytes. |
| * |
| * @param startOffset offset on page to begin the shift |
| * @param requiredBytes the number of bytes that must be freed. |
| * |
| * @exception IOException If IOException is raised during the page mod. |
| **/ |
| protected void expandPage( |
| int startOffset, |
| int requiredBytes) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(requiredBytes <= freeSpace); |
| SanityManager.ASSERT(startOffset <= firstFreeByte); |
| } |
| |
| int totalLength = firstFreeByte - startOffset; |
| |
| if (totalLength > 0) |
| { |
| System.arraycopy( |
| pageData, startOffset, |
| pageData, startOffset + requiredBytes, totalLength); |
| |
| // fix the page offsets of the rows further down the page |
| for (int slot = 0; slot < slotsInUse; slot++) |
| { |
| int offset = getRecordOffset(slot); |
| if (offset >= startOffset) |
| { |
| offset += requiredBytes; |
| setRecordOffset(slot, offset); |
| } |
| } |
| } |
| |
| freeSpace -= requiredBytes; |
| firstFreeByte += requiredBytes; |
| } |
| |
| /** |
| * Shrink page. |
| * <p> |
| * move all the data from start Offset up the page by the amount shrunk. |
| * |
| * |
| * @param startOffset offset on page to begin the shift |
| * @param shrinkBytes the number of bytes that must be moved. |
| * |
| * @exception IOException some IOException is raised during the page mod, |
| * (unlikely as this is just writing to array). |
| **/ |
| private void shrinkPage(int startOffset, int shrinkBytes) |
| throws IOException |
| { |
| // the number of bytes that needs to be moved up. |
| int totalLength = firstFreeByte - startOffset; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.DEBUG( |
| "shrinkPage", "page " + getIdentity() + |
| " shrinking " + shrinkBytes + |
| " from offset " + startOffset + |
| " to offset " + (startOffset-shrinkBytes) + |
| " moving " + totalLength + |
| " bytes. FirstFreeByte at " + firstFreeByte); |
| |
| SanityManager.ASSERT( |
| totalLength >= 0, "firstFreeByte - startOffset <= 0"); |
| |
| SanityManager.ASSERT( |
| (startOffset-shrinkBytes) > RECORD_SPACE_OFFSET , |
| "shrinking too much "); |
| |
| if (startOffset != firstFreeByte) |
| { |
| // make sure startOffset is at the beginning of a record |
| boolean foundslot = false; |
| for (int slot = 0; slot < slotsInUse; slot++) |
| { |
| if (getRecordOffset(slot) == startOffset) |
| { |
| foundslot = true; |
| break; |
| } |
| } |
| |
| if (!foundslot) |
| { |
| SanityManager.THROWASSERT( |
| "startOffset " + startOffset + |
| " not at the beginning of a record"); |
| } |
| } |
| } |
| |
| if (totalLength > 0) |
| { |
| System.arraycopy( |
| pageData, startOffset, |
| pageData, startOffset-shrinkBytes , totalLength); |
| |
| // fix the page offsets of the rows further down the page |
| for (int slot = 0; slot < slotsInUse; slot++) |
| { |
| int offset = getRecordOffset(slot); |
| if (offset >= startOffset) |
| { |
| offset -= shrinkBytes; |
| setRecordOffset(slot, offset); |
| } |
| } |
| } |
| |
| freeSpace += shrinkBytes; |
| firstFreeByte -= shrinkBytes; |
| } |
| |
| public int getRecordLength(int slot) throws IOException |
| { |
| return getRecordPortionLength(slot); |
| } |
| protected boolean getIsOverflow(int slot) throws IOException |
| { |
| return getHeaderAtSlot(slot).hasOverflow(); |
| } |
| |
| /** |
| * Log a row into the StoreOuput stream. |
| * <p> |
| * Write the row in its record format to the stream. Record format is a |
| * record header followed by each field with its field header. See this |
| * class's description for the specifics of these headers. |
| * |
| * startColumn is used to specified which column for this logRow to |
| * start logging. When realStartColumn is specified, that means part of |
| * the row has already been logged. startColumn here indicates that the |
| * first column was logged in the logBuffer, need to continue log the rest |
| * of the row starting at realStartColumn. |
| * |
| * This is used when a longColumn is encountered during a long row. |
| * After done logging the long column, we need to continue logging the |
| * rest of the row. |
| * A -1 value for realStartColumn, means that it is not significant. |
| * |
| * logRow will not throw an noSpaceOnPage exception, if it is an overflow |
| * page, and the record we are inserting is the only record on the page. |
| * We are supporting rows expanding multiple pages through this mechanism. |
| * |
| * logRow expects row to be a sparse row. |
| * <p> |
| * |
| * @return the "realStartColumn" value, -1 if not a long row. |
| * |
| * @param slot the slot of the row being logged. |
| * @param forInsert this is logging an insert (not update/delete). |
| * @param recordId record id of the row being logged. |
| * @param row actual data of row in object form. If row is |
| * null then we are logging an overflow pointer. |
| * @param validColumns bit map describing valid columns in row. |
| * @param out stream to log to. |
| * @param startColumn what column to start with (see above for detail) |
| * @param insertFlag flag indicating mode we are in, |
| * INSERT_DEFAULT - default insert |
| * INSERT_SPLIT - splitting a row/column |
| * across pages. |
| * @param realStartColumn If -1 ignore variable, else part of row has |
| * already been logged, and should continue with |
| * this column. |
| * @param realSpaceOnPage Use this as space on page if realStartColumn |
| * is not -1. |
| * @param overflowThreshold How much of the page to use before deciding |
| * to overflow a row. |
| * |
| * @exception IOException RESOLVE |
| * @exception StandardException Standard exception policy. |
| * |
| * @see BasePage#logRow |
| **/ |
| public int logRow( |
| int slot, |
| boolean forInsert, |
| int recordId, |
| Object[] row, |
| FormatableBitSet validColumns, |
| DynamicByteArrayOutputStream out, |
| int startColumn, |
| byte insertFlag, |
| int realStartColumn, |
| int realSpaceOnPage, |
| int overflowThreshold) |
| throws StandardException, IOException |
| { |
| // Is this an update that just went through handleIncompleteLogRow |
| // and handleIncompleteLogRow threw an excepiton. In this case the |
| // buffer is already finished. |
| if (!forInsert) |
| { |
| if ((realStartColumn != -1) && (realSpaceOnPage == -1)) |
| { |
| return realStartColumn; |
| } |
| } |
| |
| int spaceAvailable = freeSpace; |
| setOutputStream(out); |
| int beginPosition = out.getPosition(); |
| |
| // if we are inserting in the headPage, |
| // we need to make sure that there is enough room |
| // on the page for the reserve space. |
| userRowSize = 0; |
| boolean calcMinimumRecordSize = false; |
| |
| if (realStartColumn != (-1)) |
| { |
| // in the middle of logging a long row/column. |
| |
| spaceAvailable = realSpaceOnPage; |
| beginPosition = out.getBeginPosition(); |
| } |
| else |
| { |
| // logging row part that is on head page. |
| |
| if (!forInsert) |
| { |
| // an update can use the total space of the record, |
| // even if not all of the fields are being updated. |
| // If the updated columns will not fit then some |
| // columns will move off the page to a new chunk. |
| spaceAvailable += getTotalSpace(slot); |
| |
| } |
| else |
| { |
| // need to account for the slot table using extra space... |
| spaceAvailable -= slotEntrySize; |
| |
| if (startColumn == 0) |
| calcMinimumRecordSize = true; |
| } |
| |
| // <= is ok here as we know we want to write at least one more byte |
| if (spaceAvailable <= 0) |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| try |
| { |
| if (row == null) |
| { |
| // if the row is null, we must be writing an overflow pointer. |
| |
| return(logOverflowRecord(slot, spaceAvailable, out)); |
| } |
| |
| int numberFields = 0; |
| StoredRecordHeader recordHeader; |
| |
| if (forInsert) |
| { |
| recordHeader = new StoredRecordHeader(); |
| } |
| else |
| { |
| // Get a full copy of the record header since we might change |
| // it, and we can't modify the one on the page |
| recordHeader = |
| new StoredRecordHeader(getHeaderAtSlot(slot)); |
| |
| // an update always starts at the first column on this page |
| startColumn = recordHeader.getFirstField(); |
| } |
| |
| if (validColumns == null) |
| { |
| // all columns in row[] are valid, we will be logging them all. |
| |
| numberFields = row.length - startColumn; |
| } |
| else |
| { |
| // RESOLVE (mikem) - counting on validColumns.length may be bad |
| // for performance. |
| |
| for (int i = validColumns.getLength() - 1; |
| i >= startColumn; |
| i--) |
| { |
| if (validColumns.isSet(i)) |
| { |
| numberFields = i + 1 - startColumn; |
| break; |
| } |
| } |
| } |
| |
| int onPageNumberFields = -1; // only valid for update |
| |
| if (forInsert) |
| { |
| recordHeader.setId(recordId); |
| recordHeader.setNumberFields(numberFields); |
| } |
| else |
| { |
| // an update |
| |
| onPageNumberFields = recordHeader.getNumberFields(); |
| |
| if (numberFields > onPageNumberFields) |
| { |
| // number of fields *might* be increasing |
| if (recordHeader.hasOverflow()) |
| { |
| // other fields will be handled in next portion update |
| |
| numberFields = onPageNumberFields; |
| } |
| else |
| { |
| // number of fields is increasing |
| |
| recordHeader.setNumberFields(numberFields); |
| } |
| } |
| else if (numberFields < onPageNumberFields) |
| { |
| if (validColumns == null) |
| { |
| // number of fields is decreasing, |
| // but only allowed when the complete |
| // row is being updated. |
| recordHeader.setNumberFields(numberFields); |
| |
| // RESOLVE - |
| // need some post commit work if row has overflow |
| |
| // if (recordHeader.hasOverflow()) { |
| // remove overflow portion after commit. |
| // } |
| |
| } |
| else |
| { |
| // we process all the fields, the unchanged ones |
| // at the end will have a single byte written out |
| // indicating they are unchanged (nonexistent) |
| numberFields = onPageNumberFields; |
| } |
| } |
| } |
| |
| int endFieldExclusive = startColumn + numberFields; |
| |
| if (realStartColumn >= endFieldExclusive) |
| { |
| // The realStartColumn is greater than the last column we need |
| // to log, so we are done. |
| return (-1); |
| } |
| |
| if ((insertFlag & Page.INSERT_DEFAULT) != Page.INSERT_DEFAULT) |
| { |
| // if this is not logging the part of the row being inserted |
| // on the main page, then use startColumn as first field. |
| recordHeader.setFirstField(startColumn); |
| } |
| |
| // what column to start with? |
| |
| int firstColumn = realStartColumn; |
| if (realStartColumn == (-1)) |
| { |
| // logging on the head page. |
| |
| int recordHeaderLength = recordHeader.write(logicalDataOut); |
| |
| spaceAvailable -= recordHeaderLength; |
| if (spaceAvailable < 0) |
| { |
| // ran out of space just writing the record header. |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| firstColumn = startColumn; |
| } |
| |
| |
| boolean monitoringOldFields = false; |
| int validColumnsSize = |
| (validColumns == null) ? 0 : validColumns.getLength(); |
| |
| if (validColumns != null) |
| { |
| if (!forInsert) |
| { |
| // we monitor the length of the old fields by skipping them |
| // but only on a partial update. |
| if ((validColumns != null) && |
| (firstColumn < (startColumn + onPageNumberFields))) |
| { |
| rawDataIn.setPosition( |
| getFieldOffset(slot, firstColumn)); |
| |
| monitoringOldFields = true; |
| } |
| } |
| } |
| |
| int lastSpaceAvailable = spaceAvailable; |
| int recordSize = 0; |
| int lastColumnPositionAllowOverflow = out.getPosition(); |
| int lastColumnAllowOverflow = startColumn; |
| |
| if (spaceAvailable > OVERFLOW_POINTER_SIZE) |
| lastColumnPositionAllowOverflow = -1; |
| int columnFlag = COLUMN_FIRST; |
| |
| for (int i = firstColumn; i < endFieldExclusive; i++) |
| { |
| Object ref = null; |
| boolean ignoreColumn = false; |
| |
| |
| // should we log this column or not? |
| if ((validColumns == null) || |
| (validColumnsSize > i && validColumns.isSet(i))) |
| { |
| if (i < row.length) |
| ref = row[i]; |
| } |
| else if (!forInsert) |
| { |
| // field is not supplied, log as non-existent |
| ignoreColumn = true; |
| } |
| |
| if (spaceAvailable > OVERFLOW_POINTER_SIZE) |
| { |
| lastColumnPositionAllowOverflow = out.getPosition(); |
| lastColumnAllowOverflow = i; |
| } |
| |
| lastSpaceAvailable = spaceAvailable; |
| |
| if (ignoreColumn) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| ref == null, |
| "ref should be null for an ignored column"); |
| |
| SanityManager.ASSERT( |
| validColumns != null, |
| "validColumns should be non-null for ignored col"); |
| } |
| |
| if (i < (startColumn + onPageNumberFields)) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| monitoringOldFields, |
| "monitoringOldFields must be true"); |
| } |
| |
| // need to keep track of the old field lengths |
| // as they are remaining in the row. |
| int oldOffset = rawDataIn.getPosition(); |
| skipField(rawDataIn); |
| int oldFieldLength = |
| rawDataIn.getPosition() - oldOffset; |
| |
| if (oldFieldLength <= spaceAvailable) |
| { |
| // if field doesn't fit, |
| // spaceAvailable must be left unchanged. |
| |
| logColumn( |
| null, 0, out, Integer.MAX_VALUE, |
| COLUMN_NONE, overflowThreshold); |
| |
| spaceAvailable -= oldFieldLength; |
| } |
| |
| } |
| else |
| { |
| // this is an update that is increasing the number of |
| // columns but not providing any value. this can happen |
| // if you are updating a new column that does not |
| // actually exist in the table after using |
| // ALTER TABLE to add a couple new columns. This |
| // case is going to create actual null entries for |
| // the non-existent columns that are before the actual |
| // column being updated by the user. |
| // see DERBY-5679. |
| spaceAvailable = |
| logColumn( |
| null, 0, out, spaceAvailable, |
| COLUMN_CREATE_NULL, overflowThreshold); |
| } |
| |
| } |
| else |
| { |
| // ignoreColumn is false, we are logging this column. |
| |
| if (monitoringOldFields && |
| (i < (startColumn + onPageNumberFields))) |
| { |
| // skip the old version of the field so that |
| // rawDataIn is correctly positioned. |
| skipField(rawDataIn); |
| } |
| |
| |
| try |
| { |
| if (ref == null) |
| { |
| // no new value to provide, use the on page value. |
| spaceAvailable = |
| logColumn( |
| null, 0, out, spaceAvailable, |
| columnFlag, overflowThreshold); |
| } |
| else |
| { |
| // log the value provided in the row[i] |
| spaceAvailable = |
| logColumn( |
| row, i, out, spaceAvailable, |
| columnFlag, overflowThreshold); |
| } |
| |
| } |
| catch (LongColumnException lce) |
| { |
| // logColumn determined that the column would not fit |
| // and that the column length exceeded the long column |
| // threshold so turn this column into a long column. |
| |
| |
| if ((insertFlag & Page.INSERT_DEFAULT) == |
| Page.INSERT_DEFAULT) |
| { |
| // if default insert, just throw no space exception. |
| |
| // if the lce has throw the column as an InputStream, |
| // in the following 2 situations |
| // 1. If column came in 'row[i]' as InputStream |
| // 2. If the object stream of 'row[i]' is not |
| // null, which means that the object state of |
| // the column is null. |
| // |
| // we need to set the original InputStream column to |
| // the column that has been thrown by lce. It is a |
| // store formated InputStream which remembers all |
| // the bytes that has been read, but not yet stored. |
| // Therefore, we will not lose any bytes. |
| // |
| // In any other situation, we should not change the |
| // state of the column, |
| // i.e. if 'row[i]' has an object state, it should |
| // not be turned into an InputStream. |
| |
| if ((lce.getColumn() instanceof InputStream) |
| && (row[i] instanceof StreamStorable) ) |
| { |
| if ((row[i] instanceof InputStream) || |
| (((StreamStorable) row[i]).returnStream() |
| != null) ) |
| { |
| // change state of stream so that it uses |
| // the stream just created by the lce - |
| // which is remembering the bytes it has |
| // already read from the stream but couldn't |
| // log as there was not enough room on |
| // current page. |
| |
| ((StreamStorable) row[i]).setStream( |
| (InputStream) lce.getColumn()); |
| } |
| } |
| |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| // When one of the following two conditions is true, |
| // we will allow the insert of the long column: |
| // |
| // 1. if this is the last field, |
| // and overflow field header fits on page. |
| // 2. if it is not the last field, |
| // and overflow field header fits on page (for col) |
| // and another overflow ptr fits (for row). |
| // |
| // |
| |
| if (((spaceAvailable >= OVERFLOW_PTR_FIELD_SIZE) && |
| (i == (endFieldExclusive - 1))) || |
| ((spaceAvailable >= (OVERFLOW_PTR_FIELD_SIZE * 2))&& |
| (i < (endFieldExclusive - 1)))) |
| { |
| // If the column is a long column, it must be a |
| // InputStream. We have made the input stream into |
| // a RememberBytesInputStream, have to set the |
| // column to that, in order to preserve the bytes |
| // we already read off the stream. |
| |
| // caught a long column exception, |
| // set the variables, and rethrow the error |
| out.setBeginPosition(beginPosition); |
| lce.setExceptionInfo(out, i, spaceAvailable); |
| throw (lce); |
| } |
| } |
| } |
| |
| int nextColumn; |
| |
| recordSize += (lastSpaceAvailable - spaceAvailable); |
| boolean recordIsLong = |
| (overflowThreshold == 100) ? |
| false : isLong(recordSize, overflowThreshold); |
| |
| // get the no overflow case out of the way asap |
| if ((lastSpaceAvailable == spaceAvailable) || recordIsLong) |
| { |
| if ((insertFlag & Page.INSERT_DEFAULT) == |
| Page.INSERT_DEFAULT) |
| { |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| if (recordIsLong) |
| { |
| // if the record is long because of threshold, |
| // then, we need to reset the logicalOut. |
| // set position to the end of the previous field |
| |
| out.setPosition(out.getPosition() - recordSize); |
| } |
| |
| // did not write this column |
| nextColumn = i; |
| } |
| else |
| { |
| // assume that all fields will be written to this page. |
| nextColumn = endFieldExclusive; |
| } |
| |
| // See if we have enough room to write an overflow field if the |
| // row needs to overflow. We need overflow if we need to |
| // write another portion or another portion already exists and |
| // we will need to point to it. |
| |
| if ((lastSpaceAvailable == spaceAvailable) || |
| ((insertFlag & Page.INSERT_FOR_SPLIT) == |
| Page.INSERT_FOR_SPLIT)) |
| { |
| // The current row has filled the page. |
| |
| if (spaceAvailable < OVERFLOW_POINTER_SIZE) |
| { |
| // DERBY-4923 |
| // The fix for DERBY-4923 was to change the above |
| // check from <= to <. The test case for DERBY-4923 |
| // got the system into a state where it needed to |
| // exactly write an overflow field pointer and it |
| // had exactly OVERFLOW_POINTER_SIZE spaceAvailable, |
| // but was off by one in its check. |
| // The system insures all rows on an overflow page |
| // have at least OVERFLOW_POINTER_SIZE, so updating |
| // them should check for exactly OVERFLOW_POINTER_SIZE |
| // not <=. |
| |
| if ((i == startColumn) || |
| (lastColumnPositionAllowOverflow < 0)) |
| { |
| // not enough room for the overflow recordheader, |
| // and this is the first column on this page so |
| // need to try another page. |
| // |
| |
| if (SanityManager.DEBUG) |
| { |
| if (!forInsert) |
| { |
| // should not get into this path on an |
| // update, only on an insert. Update should |
| // reserve space on page so you can always |
| // at least update the row with a single |
| // overflow pointer. |
| SanityManager.THROWASSERT( |
| "no space to update a field on page. " + |
| "i = " + i + |
| "; startColumn = " + startColumn + |
| "; lastColumnPositionAllowOverflow = " + |
| lastColumnPositionAllowOverflow + |
| "; spaceAvailable = " + |
| spaceAvailable + |
| "; lastSpaceAvailable = " + |
| lastSpaceAvailable + |
| "; insertFlag = " + |
| insertFlag + |
| "; Page.INSERT_FOR_SPLIT = " + |
| Page.INSERT_FOR_SPLIT + |
| "; isOverflowPage() = " + |
| isOverflowPage() + |
| "; OVERFLOW_POINTER_SIZE = " + |
| OVERFLOW_POINTER_SIZE + |
| "\npage = \n" + this); |
| } |
| } |
| |
| // DERBY-4577, on an update this bug may cause |
| // the following to be thrown on an update. Update |
| // code never expects this error to be thrown, and |
| // does not handle it. The fix to DERBY-4577 was |
| // to fix insert code to make sure enough space is |
| // always reserved on overflow pages such that |
| // updates will never fail. But the fix does not |
| // affect existing problem overflow pages. If |
| // this error is encountered in a table created |
| // by software before the fix, run compress to |
| // upgrade all data in table so that error will |
| // not be encountered in future. |
| |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| else |
| { |
| // we need to go back to the last column |
| // that left enough room for an overflow pointer. |
| |
| out.setPosition(lastColumnPositionAllowOverflow); |
| nextColumn = lastColumnAllowOverflow; |
| } |
| } |
| } |
| |
| if (nextColumn < endFieldExclusive) |
| { |
| // If the number of cols has been reduced. |
| |
| int actualNumberFields = nextColumn - startColumn; |
| |
| // go back and update that numberFields in recordHeader. |
| // no need to update spaceAvailable here, because if we are |
| // here, we will be returning any way, and spaceAvailable |
| // will be thrown away. |
| |
| int oldSize = recordHeader.size(); |
| recordHeader.setNumberFields(actualNumberFields); |
| |
| int newSize = recordHeader.size(); |
| |
| // now we are ready to write the new record header. |
| int endPosition = out.getPosition(); |
| |
| if (oldSize > newSize) |
| { |
| // if the old size is bigger than the new size, then |
| // leave extra bytes at the beginning of byte stream. |
| |
| int delta = oldSize - newSize; |
| out.setBeginPosition(beginPosition + delta); |
| out.setPosition(beginPosition + delta); |
| } |
| else if (newSize > oldSize) |
| { |
| out.setPosition(beginPosition); |
| |
| } |
| else |
| { |
| out.setBeginPosition(beginPosition); |
| out.setPosition(beginPosition); |
| } |
| |
| int realLen = recordHeader.write(logicalDataOut); |
| if (SanityManager.DEBUG) |
| { |
| if ((realLen + (oldSize - newSize)) != oldSize) |
| { |
| SanityManager.THROWASSERT( |
| "recordHeader size incorrect. realLen = " + |
| realLen + ", delta = " + |
| (oldSize - newSize) + ", oldSize = " + oldSize); |
| } |
| } |
| |
| out.setPosition(endPosition); |
| |
| if (!forInsert) |
| { |
| // The update is incomplete, fields beyond this |
| // point will have to move off the page. For any fields |
| // that are not being updated we have to save their |
| // values from this page to insert into an overflow |
| // portion. |
| // |
| // When the complete row is being updated there is no |
| // need to save any fields so just return. |
| if (validColumns != null) |
| { |
| handleIncompleteLogRow( |
| slot, nextColumn, validColumns, out); |
| } |
| } |
| |
| return (nextColumn); |
| } |
| |
| columnFlag = COLUMN_NONE; |
| } |
| |
| out.setBeginPosition(beginPosition); |
| startColumn = -1; |
| |
| if ((calcMinimumRecordSize) && |
| (spaceAvailable < (minimumRecordSize - userRowSize))) |
| { |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| } |
| finally |
| { |
| resetOutputStream(); |
| } |
| |
| return (startColumn); |
| } |
| |
| /** |
| * Handle an update of a record portion that is incomplete. |
| * <p> |
| * Handle an update of a record portion that is incomplete. |
| * Ie. Columns have expanded that require other columns to move |
| * off the page into a new portion. |
| * <P> |
| * This method works out of the columns that need to be moved which are not |
| * being updated and makes a copy of their data. It then throws an |
| * exception with this data, much like the long column exception which will |
| * then allow the original insert to complete. |
| * <P> |
| * If no columns need to be saved (ie all the ones that would move are |
| * being updated) then no exception is thrown, logRow() will return and the |
| * update completes normally. |
| * <p> |
| * |
| * @param slot slot of the current update. |
| * @param startColumn column to start at, handles start in middle of row |
| * @param columnList bit map indicating which columns are being updated. |
| * @param out place to lot to. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private void handleIncompleteLogRow( |
| int slot, |
| int startColumn, |
| FormatableBitSet columnList, |
| DynamicByteArrayOutputStream out) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(columnList != null); |
| |
| StoredRecordHeader rh = getHeaderAtSlot(slot); |
| |
| int endFieldExclusive = rh.getFirstField() + rh.getNumberFields(); |
| |
| // first see if any fields are not being modified |
| boolean needSave = false; |
| int columnListSize = columnList.size(); |
| for (int i = startColumn; i < endFieldExclusive; i++) |
| { |
| if (!(columnListSize > i && columnList.get(i))) |
| { |
| needSave = true; |
| break; |
| } |
| } |
| if (!needSave) |
| return; |
| |
| Object[] savedFields = |
| new Object[endFieldExclusive - startColumn]; |
| |
| ByteArrayOutputStream fieldStream = null; |
| |
| for (int i = startColumn; i < endFieldExclusive; i++) |
| { |
| // row is being updated - ignore |
| if (columnListSize > i && columnList.get(i)) |
| continue; |
| |
| // save the data |
| |
| try |
| { |
| // use the old value - we use logField to ensure that we |
| // get the raw contents of the field and don't follow |
| // any long columns. In addition we save this as a RawField |
| // so that we preserve the state of the field header. |
| if (fieldStream == null) |
| fieldStream = new ByteArrayOutputStream(); |
| else |
| fieldStream.reset(); |
| |
| logField(slot, i, fieldStream); |
| |
| savedFields[i - startColumn] = |
| new RawField(fieldStream.toByteArray()); |
| |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, getPageId())); |
| } |
| } |
| |
| // Use a long column exception to notify the caller of the need |
| // to perform an insert of the columns that need to move |
| |
| LongColumnException lce = new LongColumnException(); |
| lce.setExceptionInfo( |
| out, startColumn, -1 /* indicates not actual long column */); |
| lce.setColumn(savedFields); |
| |
| throw lce; |
| } |
| |
| /** |
| |
| @param row (IN/OUT) the row that is to be restored (sparse representation) |
| @param limitInput the limit input stream |
| @param objectInput the object input stream |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException I/O exception in reading meta data. |
| */ |
| |
| /** |
| * Restore a storable row from a LimitInputStream. |
| * <p> |
| * Restore a storable row from an LimitInputStream - user must supply two |
| * streams on top of the same data, one implements ObjectInput interface |
| * that knows how to restore the object, the other one implements |
| * LimitInputStream. |
| * <p> |
| * @param in the limit input stream |
| * @param row (IN/OUT) row that is to be restored |
| * (sparse representation) |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public void restoreRecordFromStream( |
| LimitObjectInput in, |
| Object[] row) |
| throws StandardException, IOException |
| { |
| |
| StoredRecordHeader recordHeader = new StoredRecordHeader(); |
| recordHeader.read(in); |
| readRecordFromStream( |
| row, |
| row.length - 1, |
| (int[]) null, |
| (int[]) null, |
| in, |
| recordHeader, |
| null); |
| } |
| |
| /** |
| * Process the qualifier list on the row, return true if it qualifies. |
| * <p> |
| * A two dimensional array is to be used to pass around a AND's and OR's in |
| * conjunctive normal form. The top slot of the 2 dimensional array is |
| * optimized for the more frequent where no OR's are present. The first |
| * array slot is always a list of AND's to be treated as described above |
| * for single dimensional AND qualifier arrays. The subsequent slots are |
| * to be treated as AND'd arrays or OR's. Thus the 2 dimensional array |
| * qual[][] argument is to be treated as the following, note if |
| * qual.length = 1 then only the first array is valid and it is and an |
| * array of and clauses: |
| * |
| * (qual[0][0] and qual[0][0] ... and qual[0][qual[0].length - 1]) |
| * and |
| * (qual[1][0] or qual[1][1] ... or qual[1][qual[1].length - 1]) |
| * and |
| * (qual[2][0] or qual[2][1] ... or qual[2][qual[2].length - 1]) |
| * ... |
| * and |
| * (qual[qual.length - 1][0] or qual[1][1] ... or qual[1][2]) |
| * |
| * |
| * @return true if the row qualifies. |
| * |
| * @param row The row being qualified. |
| * @param qual_list 2 dimensional array representing conjunctive |
| * normal form of simple qualifiers. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private boolean qualifyRecordFromRow( |
| Object[] row, |
| Qualifier[][] qual_list) |
| throws StandardException |
| { |
| boolean row_qualifies = true; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(row != null); |
| } |
| |
| // First do the qual[0] which is an array of qualifer terms. |
| |
| if (SanityManager.DEBUG) |
| { |
| // routine should not be called if there is no qualifier |
| SanityManager.ASSERT(qual_list != null); |
| SanityManager.ASSERT(qual_list.length > 0); |
| } |
| |
| for (int i = 0; i < qual_list[0].length; i++) |
| { |
| // process each AND clause |
| |
| row_qualifies = false; |
| |
| // process each OR clause. |
| |
| Qualifier q = qual_list[0][i]; |
| |
| // Get the column from the possibly partial row, of the |
| // q.getColumnId()'th column in the full row. |
| DataValueDescriptor columnValue = |
| (DataValueDescriptor) row[q.getColumnId()]; |
| |
| row_qualifies = |
| columnValue.compare( |
| q.getOperator(), |
| q.getOrderable(), |
| q.getOrderedNulls(), |
| q.getUnknownRV()); |
| |
| if (q.negateCompareResult()) |
| row_qualifies = !row_qualifies; |
| |
| // Once an AND fails the whole Qualification fails - do a return! |
| if (!row_qualifies) |
| return(false); |
| } |
| |
| // all the qual[0] and terms passed, now process the OR clauses |
| |
| for (int and_idx = 1; and_idx < qual_list.length; and_idx++) |
| { |
| // loop through each of the "and" clause. |
| |
| row_qualifies = false; |
| |
| if (SanityManager.DEBUG) |
| { |
| // Each OR clause must be non-empty. |
| SanityManager.ASSERT(qual_list[and_idx].length > 0); |
| } |
| |
| for (int or_idx = 0; or_idx < qual_list[and_idx].length; or_idx++) |
| { |
| // Apply one qualifier to the row. |
| Qualifier q = qual_list[and_idx][or_idx]; |
| int col_id = q.getColumnId(); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| (col_id < row.length), |
| "Qualifier is referencing a column not in the row."); |
| } |
| |
| // Get the column from the possibly partial row, of the |
| // q.getColumnId()'th column in the full row. |
| DataValueDescriptor columnValue = |
| (DataValueDescriptor) row[q.getColumnId()]; |
| |
| if (SanityManager.DEBUG) |
| { |
| if (columnValue == null) |
| SanityManager.THROWASSERT( |
| "1:row = " + RowUtil.toString(row) + |
| "row.length = " + row.length + |
| ";q.getColumnId() = " + q.getColumnId()); |
| } |
| |
| // do the compare between the column value and value in the |
| // qualifier. |
| row_qualifies = |
| columnValue.compare( |
| q.getOperator(), |
| q.getOrderable(), |
| q.getOrderedNulls(), |
| q.getUnknownRV()); |
| |
| if (q.negateCompareResult()) |
| row_qualifies = !row_qualifies; |
| |
| // SanityManager.DEBUG_PRINT("StoredPage.qual", "processing qual[" + and_idx + "][" + or_idx + "] = " + qual_list[and_idx][or_idx] ); |
| |
| // SanityManager.DEBUG_PRINT("StoredPage.qual", "value = " + row_qualifies); |
| |
| // processing "OR" clauses, so as soon as one is true, break |
| // to go and process next AND clause. |
| if (row_qualifies) |
| break; |
| |
| } |
| |
| // The qualifier list represented a set of "AND'd" |
| // qualifications so as soon as one is false processing is done. |
| if (!row_qualifies) |
| break; |
| } |
| |
| return(row_qualifies); |
| } |
| |
| /** |
| * Read just one column from stream into row. |
| * <p> |
| * The routine reads just one column from the row, it is mostly code |
| * taken from readRecordFromStream, but highly optimized to just get |
| * one column from a non-overflow row. It can only be called to read |
| * a row from the pageData array as it directly accesses the page array |
| * to avoid the Stream overhead while processing non-user data which |
| * does not need the limit functionality. |
| * <p> |
| * It is expected that this code will be called to read in a column |
| * associated with a qualifiers which are applied one column at a time, |
| * and has been specialized to proved the greatest peformance for |
| * processing qualifiers. This kind of access is done when scanning |
| * large datasets while applying qualifiers and thus any performance |
| * gain at this low level is multiplied by the large number of rows that |
| * may be iterated over. |
| * <p> |
| * The column is read into the object located in row[qual_colid]. |
| * |
| * @param row col is read into object in row[qual_colid]. |
| * @param offset_to_field_data offset in bytes from top of page to field |
| * @param colid the column id to read, colid N is row[N] |
| * @param recordHeader record header of row to read column from. |
| * @param recordToLock record handle to lock, |
| * used by overflow column code. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private final void readOneColumnFromPage( |
| Object[] row, |
| int colid, |
| int offset_to_field_data, |
| StoredRecordHeader recordHeader, |
| RecordHandle recordToLock) |
| throws StandardException, IOException |
| { |
| ErrorObjectInput inUserCode = null; |
| |
| // Reads in this routine are always against the raw data in the |
| // pageData array, thus it can assume array access to page data array. |
| ArrayInputStream lrdi = rawDataIn; |
| |
| try |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (colid >= row.length) |
| SanityManager.THROWASSERT( |
| "colid = " + colid + |
| ";row length = " + row.length); |
| |
| // currently this routine will not work on long rows. |
| if (recordHeader.getFirstField() != 0) |
| { |
| SanityManager.THROWASSERT( |
| "recordHeader.getFirstField() = " + |
| recordHeader.getFirstField()); |
| } |
| } |
| |
| Object column = row[colid]; |
| |
| // if the column id exists on this page. |
| if (colid <= (recordHeader.getNumberFields() - 1)) |
| { |
| // skip the fields before colid, the column in question |
| // existent on this page. |
| |
| for (int columnId = colid; columnId > 0; columnId--) |
| { |
| offset_to_field_data += |
| StoredFieldHeader.readTotalFieldLength( |
| pageData, offset_to_field_data); |
| } |
| |
| |
| |
| // read the field header |
| |
| // read the status byte. |
| int fieldStatus = |
| StoredFieldHeader.readStatus( |
| pageData, offset_to_field_data); |
| |
| // read the field data length, and position on 1st byte of data. |
| int fieldDataLength = |
| StoredFieldHeader.readFieldLengthAndSetStreamPosition( |
| pageData, |
| offset_to_field_data + |
| StoredFieldHeader.STORED_FIELD_HEADER_STATUS_SIZE, |
| fieldStatus, |
| slotFieldSize, |
| lrdi); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| !StoredFieldHeader.isExtensible(fieldStatus), |
| "extensible fields not supported yet"); |
| } |
| |
| // SRW-DJD code assumes non-extensible case ... |
| |
| if (!StoredFieldHeader.isNonexistent(fieldStatus)) |
| { |
| boolean isOverflow = |
| StoredFieldHeader.isOverflow(fieldStatus); |
| |
| OverflowInputStream overflowIn = null; |
| |
| if (isOverflow) |
| { |
| // A fetched long column is returned as a stream |
| long overflowPage = |
| CompressedNumber.readLong((InputStream) lrdi); |
| |
| int overflowId = |
| CompressedNumber.readInt((InputStream) lrdi); |
| |
| // Prepare the stream for results... |
| // create the byteHolder the size of a page, so, that it |
| // will fit the field Data that would fit on a page. |
| MemByteHolder byteHolder = |
| new MemByteHolder(pageData.length); |
| |
| overflowIn = new OverflowInputStream( |
| byteHolder, owner, overflowPage, |
| overflowId, recordToLock); |
| } |
| |
| // Deal with Storable columns |
| if (column instanceof DataValueDescriptor) |
| { |
| DataValueDescriptor sColumn = |
| (DataValueDescriptor) column; |
| |
| // is the column null ? |
| if (StoredFieldHeader.isNull(fieldStatus)) |
| { |
| sColumn.restoreToNull(); |
| } |
| else |
| { |
| // set the limit for the user read |
| if (!isOverflow) |
| { |
| // normal, non-overflow column case. |
| |
| lrdi.setLimit(fieldDataLength); |
| inUserCode = lrdi; |
| sColumn.readExternalFromArray(lrdi); |
| inUserCode = null; |
| int unread = lrdi.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(lrdi, unread); |
| } |
| else |
| { |
| // fetched column is a Storable long column. |
| |
| FormatIdInputStream newIn = |
| new FormatIdInputStream(overflowIn); |
| |
| if ((sColumn instanceof StreamStorable)) |
| { |
| ((StreamStorable)sColumn).setStream(newIn); |
| } |
| else |
| { |
| inUserCode = newIn; |
| sColumn.readExternal(newIn); |
| inUserCode = null; |
| } |
| } |
| } |
| } |
| else |
| { |
| // At this point only non-Storable columns. |
| |
| if (StoredFieldHeader.isNull(fieldStatus)) |
| { |
| // Only Storables can be null ... |
| |
| throw StandardException.newException( |
| SQLState.DATA_NULL_STORABLE_COLUMN, |
| Integer.toString(colid)); |
| } |
| |
| // This is a non-extensible field, which means the |
| // caller must know the correct type and thus the |
| // element in row is the correct type or null. It must |
| // be Serializable. |
| // |
| // We do not support Externalizable here. |
| |
| lrdi.setLimit(fieldDataLength); |
| inUserCode = lrdi; |
| // RESOLVE (no non-storables?) |
| row[colid] = (Object) lrdi.readObject(); |
| inUserCode = null; |
| int unread = lrdi.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(lrdi, unread); |
| } |
| |
| } |
| else |
| { |
| // column does not exist in the row, return null. |
| |
| // field is non-existent |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[colid] = null; |
| } |
| } |
| } |
| else |
| { |
| // field does not exist on this page. |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[colid] = null; |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| |
| if (inUserCode != null) |
| { |
| lrdi.clearLimit(); |
| |
| if (ioe instanceof EOFException) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "StoredPage.readOneColumnFromPage - EOF while restoring record: " + |
| recordHeader + |
| "Page dump = " + this); |
| SanityManager.showTrace(ioe); |
| } |
| |
| // going beyond the limit in a DataInput class results in |
| // an EOFException when it sees the -1 from a read |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISMATCH, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // some SQLData error reporting |
| Exception ne = inUserCode.getNestedException(); |
| if (ne != null) |
| { |
| if (ne instanceof InstantiationException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_INSTANTIATION_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof IllegalAccessException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_ILLEGAL_ACCESS_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof StandardException) |
| { |
| throw (StandardException) ne; |
| } |
| } |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // re-throw to higher levels so they can put it in correct context. |
| throw ioe; |
| |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| lrdi.clearLimit(); |
| |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISSING_CLASS, |
| cnfe, inUserCode.getErrorInfo()); |
| |
| } |
| catch (LinkageError le) |
| { |
| // Some error during the link of a user class |
| if (inUserCode != null) |
| { |
| lrdi.clearLimit(); |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| le, inUserCode.getErrorInfo()); |
| } |
| throw le; |
| } |
| |
| } |
| |
| |
| |
| /** |
| * Process the list of qualifiers on the row in the stream. |
| * <p> |
| * The rawDataIn stream is expected to be positioned after the record |
| * header. |
| * <p> |
| * Check all qualifiers in the qualifier array against row. Return true |
| * if all compares specified by the qualifier array return true, else |
| * return false. |
| * <p> |
| * This routine assumes client caller has already checked if the row |
| * is deleted or not. The row that it get's is expected to match |
| * the partial column list of the scan. |
| * <p> |
| * On entering this routine the stream should be positioned to the |
| * beginning of the row data, just after the row header. On exit |
| * the stream will also be positioned there. |
| * |
| * A two dimensional array is to be used to pass around a AND's and OR's in |
| * conjunctive normal form. The top slot of the 2 dimensional array is |
| * optimized for the more frequent where no OR's are present. The first |
| * array slot is always a list of AND's to be treated as described above |
| * for single dimensional AND qualifier arrays. The subsequent slots are |
| * to be treated as AND'd arrays or OR's. Thus the 2 dimensional array |
| * qual[][] argument is to be treated as the following, note if |
| * qual.length = 1 then only the first array is valid and it is and an |
| * array of and clauses: |
| * |
| * (qual[0][0] and qual[0][0] ... and qual[0][qual[0].length - 1]) |
| * and |
| * (qual[1][0] or qual[1][1] ... or qual[1][qual[1].length - 1]) |
| * and |
| * (qual[2][0] or qual[2][1] ... or qual[2][qual[2].length - 1]) |
| * ... |
| * and |
| * (qual[qual.length - 1][0] or qual[1][1] ... or qual[1][2]) |
| * |
| * @return Whether or not the row input qualifies. |
| * |
| * @param row restore row into this object array. |
| * @param offset_to_row_data offset in bytes from top of page to row |
| * @param fetchDesc Description of fetch including which cols |
| * and qualifiers. |
| * @param recordHeader The record header of the row, it was read |
| * in from stream and dataIn is positioned |
| * after it. |
| * @param recordToLock The head row to use for locking, used to |
| * lock head row of overflow columns/rows. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private final boolean qualifyRecordFromSlot( |
| Object[] row, |
| int offset_to_row_data, |
| FetchDescriptor fetchDesc, |
| StoredRecordHeader recordHeader, |
| RecordHandle recordToLock) |
| throws StandardException, IOException |
| { |
| boolean row_qualifies = true; |
| Qualifier[][] qual_list = fetchDesc.getQualifierList(); |
| int[] materializedCols = fetchDesc.getMaterializedColumns(); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(qual_list != null, "Not coded yet!"); |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(row != null); |
| } |
| |
| // First process the initial list of AND's in the 1st array |
| |
| for (int i = 0; i < qual_list[0].length; i++) |
| { |
| // process each AND clause |
| |
| row_qualifies = false; |
| |
| // Apply one qualifier to the row. |
| Qualifier q = qual_list[0][i]; |
| int col_id = q.getColumnId(); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| (col_id < row.length), |
| "Qualifier is referencing a column not in the row."); |
| } |
| |
| // materialize the column object if we haven't done it yet. |
| if (materializedCols[col_id] == 0) |
| { |
| // materialize just this column from the row, no qualifiers |
| readOneColumnFromPage( |
| row, |
| col_id, |
| offset_to_row_data, |
| recordHeader, |
| recordToLock); |
| |
| // mark offset, indicating the row has been read in. |
| // |
| // RESOLVE (mikem) - right now value of entry is useless, it |
| // is an int so that in the future we could cache the offset |
| // to fields to improve performance of getting to a column |
| // after qualifying. |
| materializedCols[col_id] = offset_to_row_data; |
| } |
| |
| // Get the column from the possibly partial row, of the |
| // q.getColumnId()'th column in the full row. |
| |
| if (SanityManager.DEBUG) |
| { |
| if (row[col_id] == null) |
| SanityManager.THROWASSERT( |
| "1:row = " + RowUtil.toString(row) + |
| "row.length = " + row.length + |
| ";q.getColumnId() = " + q.getColumnId()); |
| } |
| |
| // do the compare between the column value and value in the |
| // qualifier. |
| row_qualifies = |
| ((DataValueDescriptor) row[col_id]).compare( |
| q.getOperator(), |
| q.getOrderable(), |
| q.getOrderedNulls(), |
| q.getUnknownRV()); |
| |
| if (q.negateCompareResult()) |
| row_qualifies = !row_qualifies; |
| |
| // Once an AND fails the whole Qualification fails - do a return! |
| if (!row_qualifies) |
| return(false); |
| } |
| |
| // Now process the Subsequent OR clause's, beginning with qual_list[1] |
| |
| for (int and_idx = 1; and_idx < qual_list.length; and_idx++) |
| { |
| // loop through each of the "and" clause. |
| |
| row_qualifies = false; |
| |
| for (int or_idx = 0; or_idx < qual_list[and_idx].length; or_idx++) |
| { |
| // Apply one qualifier to the row. |
| Qualifier q = qual_list[and_idx][or_idx]; |
| int col_id = q.getColumnId(); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| (col_id < row.length), |
| "Qualifier is referencing a column not in the row."); |
| } |
| |
| // materialize the column object if we haven't done it yet. |
| if (materializedCols[col_id] == 0) |
| { |
| // materialize just this column from the row, no qualifiers |
| readOneColumnFromPage( |
| row, |
| col_id, |
| offset_to_row_data, |
| recordHeader, |
| recordToLock); |
| |
| // mark offset, indicating the row has been read in. |
| // |
| // RESOLVE (mikem) - right now value of entry is useless, it |
| // is an int so that in the future we could cache the offset |
| // to fields to improve performance of getting to a column |
| // after qualifying. |
| materializedCols[col_id] = offset_to_row_data; |
| } |
| |
| // Get the column from the possibly partial row, of the |
| // q.getColumnId()'th column in the full row. |
| |
| if (SanityManager.DEBUG) |
| { |
| if (row[col_id] == null) |
| SanityManager.THROWASSERT( |
| "1:row = " + RowUtil.toString(row) + |
| "row.length = " + row.length + |
| ";q.getColumnId() = " + q.getColumnId()); |
| } |
| |
| // do the compare between the column value and value in the |
| // qualifier. |
| row_qualifies = |
| ((DataValueDescriptor) row[col_id]).compare( |
| q.getOperator(), |
| q.getOrderable(), |
| q.getOrderedNulls(), |
| q.getUnknownRV()); |
| |
| |
| if (q.negateCompareResult()) |
| row_qualifies = !row_qualifies; |
| |
| // SanityManager.DEBUG_PRINT("StoredPage.qual", "processing qual[" + and_idx + "][" + or_idx + "] = " + qual_list[and_idx][or_idx] ); |
| |
| // SanityManager.DEBUG_PRINT("StoredPage.qual", "value = " + row_qualifies); |
| |
| // processing "OR" clauses, so as soon as one is true, break |
| // to go and process next AND clause. |
| if (row_qualifies) |
| break; |
| |
| } |
| |
| // The qualifier list represented a set of "AND'd" |
| // qualifications so as soon as one is false processing is done. |
| if (!row_qualifies) |
| break; |
| } |
| |
| return(row_qualifies); |
| } |
| |
| /** |
| * restore a record from a stream. |
| * <p> |
| * The rawDataIn stream is expected to be positioned after the record |
| * header. |
| * |
| * @return The identifier to be used to open the conglomerate later. |
| * |
| * @param row restore row into this object array. |
| * @param max_colid The maximum numbered column id that will be |
| * requested by caller. It should be: |
| * min(row.length - 1, maximum bit set in vCols) |
| * It is used to stop the inner most loop from |
| * looking at more columns in the row. |
| * @param vCols If not null, bit map indicates valid cols. |
| * @param mCols If not null, int array indicates columns already |
| * read in from the stream. A non-zero entry |
| * means the column has already been read in. |
| * @param dataIn restore row from this stream. |
| * @param recordHeader The record header of the row, it was read in |
| * from stream and dataIn is positioned after it. |
| * @param recordToLock The head row to use for locking, used to lock |
| * head row of overflow columns/rows. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private final boolean readRecordFromStream( |
| Object[] row, |
| int max_colid, |
| int[] vCols, |
| int[] mCols, |
| LimitObjectInput dataIn, |
| StoredRecordHeader recordHeader, |
| RecordHandle recordToLock) |
| throws StandardException, IOException |
| { |
| ErrorObjectInput inUserCode = null; |
| try |
| { |
| // Get the number of columns in the row. |
| int numberFields = recordHeader.getNumberFields(); |
| |
| int startColumn = recordHeader.getFirstField(); |
| |
| if (startColumn > max_colid) |
| { |
| // done if the startColumn is higher than highest column. |
| return true; |
| } |
| |
| // For each column in the row, restore the column from |
| // the corresponding field in the record. If the field |
| // is missing or not set, set the column to null. |
| |
| int highestColumnOnPage = numberFields + startColumn; |
| |
| int vColsSize = (vCols == null ) ? 0 : vCols.length; |
| |
| for (int columnId = startColumn; columnId <= max_colid; columnId++) |
| { |
| // skip any "existing" columns not requested, or requested cols |
| // that have already been read. |
| if (((vCols != null) && |
| (!(vColsSize > columnId && (vCols[columnId] != 0)))) || |
| ((mCols != null) && (mCols[columnId] != 0))) |
| { |
| if (columnId < highestColumnOnPage) |
| { |
| // If the field exists in the row on the page, but the |
| // partial row being returned does not include it, |
| // skip the field ... |
| |
| skipField(dataIn); |
| } |
| |
| continue; |
| } |
| |
| // See if the column identifier is beyond the number of fields |
| // that this record has |
| if (columnId >= highestColumnOnPage) |
| { |
| // field is non-existent |
| Object column = row[columnId]; |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[columnId] = null; |
| } |
| continue; |
| } |
| |
| // read the field header |
| int fieldStatus = |
| StoredFieldHeader.readStatus(dataIn); |
| |
| int fieldDataLength = |
| StoredFieldHeader.readFieldDataLength( |
| dataIn, fieldStatus, slotFieldSize); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| !StoredFieldHeader.isExtensible(fieldStatus), |
| "extensible fields not supported yet"); |
| } |
| |
| Object column = row[columnId]; |
| |
| OverflowInputStream overflowIn = null; |
| |
| // SRW-DJD code assumes non-extensible case ... |
| |
| // field is non-existent, return null |
| if (StoredFieldHeader.isNonexistent(fieldStatus)) |
| { |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[columnId] = null; |
| } |
| continue; |
| } |
| |
| boolean isOverflow = StoredFieldHeader.isOverflow(fieldStatus); |
| |
| if (isOverflow) |
| { |
| |
| // A fetched long column needs to be returned as a stream |
| // |
| long overflowPage = |
| CompressedNumber.readLong((InputStream) dataIn); |
| |
| int overflowId = |
| CompressedNumber.readInt((InputStream) dataIn); |
| |
| // Prepare the stream for results... |
| // create the byteHolder the size of a page, so, that it |
| // will fit the field Data that would fit on a page. |
| MemByteHolder byteHolder = |
| new MemByteHolder(pageData.length); |
| |
| overflowIn = new OverflowInputStream( |
| byteHolder, owner, overflowPage, |
| overflowId, recordToLock); |
| } |
| |
| // Deal with Object columns |
| if (column instanceof DataValueDescriptor) |
| { |
| DataValueDescriptor sColumn = (DataValueDescriptor) column; |
| |
| // is the column null ? |
| if (StoredFieldHeader.isNull(fieldStatus)) |
| { |
| sColumn.restoreToNull(); |
| continue; |
| } |
| |
| // set the limit for the user read |
| if (!isOverflow) |
| { |
| // normal, non-overflow column case. |
| |
| dataIn.setLimit(fieldDataLength); |
| inUserCode = dataIn; |
| sColumn.readExternal(dataIn); |
| inUserCode = null; |
| int unread = dataIn.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(dataIn, unread); |
| } |
| else |
| { |
| // column being fetched is a Object long column. |
| |
| FormatIdInputStream newIn = |
| new FormatIdInputStream(overflowIn); |
| |
| // if a column is a long column, store recommends user |
| // fetch it as a stream. |
| boolean fetchStream = true; |
| |
| if (!(sColumn instanceof StreamStorable)) |
| { |
| fetchStream = false; |
| } |
| |
| if (fetchStream) |
| { |
| ((StreamStorable)sColumn).setStream(newIn); |
| } |
| else |
| { |
| inUserCode = newIn; |
| sColumn.readExternal(newIn); |
| inUserCode = null; |
| } |
| |
| } |
| |
| continue; |
| } |
| |
| // At this point only non-Storable columns. |
| |
| if (StoredFieldHeader.isNull(fieldStatus)) |
| { |
| // Only Storables can be null ... |
| |
| throw StandardException.newException( |
| SQLState.DATA_NULL_STORABLE_COLUMN, |
| Integer.toString(columnId)); |
| } |
| |
| // This is a non-extensible field, which means the caller must |
| // know the correct type and thus the element in row is the |
| // correct type or null. It must be Serializable. |
| // |
| // We do not support Externalizable here. |
| |
| dataIn.setLimit(fieldDataLength); |
| inUserCode = dataIn; |
| row[columnId] = (Object) dataIn.readObject(); |
| inUserCode = null; |
| int unread = dataIn.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(dataIn, unread); |
| |
| continue; |
| } |
| |
| // if the last column on this page is bigger than the highest |
| // column we are looking for, then we are done restoring the record. |
| |
| if ((numberFields + startColumn) > max_colid) |
| return true; |
| else |
| return false; |
| |
| } |
| catch (IOException ioe) |
| { |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| |
| if (inUserCode != null) |
| { |
| dataIn.clearLimit(); |
| |
| if (ioe instanceof EOFException) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "StoredPage - EOF while restoring record: " + |
| recordHeader + |
| "Page dump = " + this); |
| } |
| |
| // going beyond the limit in a DataInput class results in |
| // an EOFException when it sees the -1 from a read |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISMATCH, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // some SQLData error reporting |
| Exception ne = inUserCode.getNestedException(); |
| if (ne != null) |
| { |
| if (ne instanceof InstantiationException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_INSTANTIATION_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof IllegalAccessException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_ILLEGAL_ACCESS_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof StandardException) |
| { |
| throw (StandardException) ne; |
| } |
| } |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // re-throw to higher levels so they can put it in correct context. |
| throw ioe; |
| |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| dataIn.clearLimit(); |
| |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISSING_CLASS, |
| cnfe, inUserCode.getErrorInfo()); |
| |
| } |
| catch (LinkageError le) |
| { |
| // Some error during the link of a user class |
| if (inUserCode != null) |
| { |
| dataIn.clearLimit(); |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| le, inUserCode.getErrorInfo()); |
| } |
| throw le; |
| } |
| |
| } |
| |
| private final boolean readRecordFromArray( |
| Object[] row, |
| int max_colid, |
| int[] vCols, |
| int[] mCols, |
| ArrayInputStream dataIn, |
| StoredRecordHeader recordHeader, |
| RecordHandle recordToLock) |
| throws StandardException, IOException |
| { |
| ErrorObjectInput inUserCode = null; |
| try |
| { |
| // Get the number of columns in the row. |
| int numberFields = recordHeader.getNumberFields(); |
| |
| int startColumn = recordHeader.getFirstField(); |
| |
| if (startColumn > max_colid) |
| { |
| // done if the startColumn is higher than highest column. |
| return true; |
| } |
| |
| // For each column in the row, restore the column from |
| // the corresponding field in the record. If the field |
| // is missing or not set, set the column to null. |
| |
| int highestColumnOnPage = numberFields + startColumn; |
| |
| int vColsSize = (vCols == null ) ? 0 : vCols.length; |
| |
| int offset_to_field_data = dataIn.getPosition(); |
| |
| for (int columnId = startColumn; columnId <= max_colid; columnId++) |
| { |
| // skip any "existing" columns not requested, or requested cols |
| // that have already been read. |
| if (((vCols != null) && |
| (!(vColsSize > columnId && (vCols[columnId] != 0)))) || |
| ((mCols != null) && (mCols[columnId] != 0))) |
| { |
| if (columnId < highestColumnOnPage) |
| { |
| // If the field exists in the row on the page, but the |
| // partial row being returned does not include it, |
| // skip the field ... |
| |
| offset_to_field_data += |
| StoredFieldHeader.readTotalFieldLength( |
| pageData, offset_to_field_data); |
| } |
| |
| continue; |
| } |
| else if (columnId < highestColumnOnPage) |
| { |
| // the column is on this page. |
| |
| // read the field header |
| |
| // read the status byte. |
| int fieldStatus = |
| StoredFieldHeader.readStatus( |
| pageData, offset_to_field_data); |
| |
| // read the field data length, position on 1st byte of data |
| int fieldDataLength = |
| StoredFieldHeader.readFieldLengthAndSetStreamPosition( |
| pageData, |
| offset_to_field_data + |
| StoredFieldHeader.STORED_FIELD_HEADER_STATUS_SIZE, |
| fieldStatus, |
| slotFieldSize, |
| dataIn); |
| |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| !StoredFieldHeader.isExtensible(fieldStatus), |
| "extensible fields not supported yet"); |
| } |
| |
| Object column = row[columnId]; |
| |
| OverflowInputStream overflowIn = null; |
| |
| // SRW-DJD code assumes non-extensible case ... |
| |
| if ((fieldStatus & StoredFieldHeader.FIELD_NONEXISTENT) != |
| StoredFieldHeader.FIELD_NONEXISTENT) |
| { |
| // normal path - field exists. |
| |
| boolean isOverflow = |
| ((fieldStatus & |
| StoredFieldHeader.FIELD_OVERFLOW) != 0); |
| |
| if (isOverflow) |
| { |
| |
| // A fetched long column is returned as a stream |
| |
| long overflowPage = |
| CompressedNumber.readLong((InputStream) dataIn); |
| |
| int overflowId = |
| CompressedNumber.readInt((InputStream) dataIn); |
| |
| // Prepare the stream for results... |
| // create the byteHolder the size of a page, so, |
| // that it will fit the field Data that would fit |
| // on a page. |
| |
| MemByteHolder byteHolder = |
| new MemByteHolder(pageData.length); |
| |
| overflowIn = new OverflowInputStream( |
| byteHolder, owner, overflowPage, |
| overflowId, recordToLock); |
| } |
| |
| // Deal with Object columns |
| if (column instanceof DataValueDescriptor) |
| { |
| DataValueDescriptor sColumn = |
| (DataValueDescriptor) column; |
| |
| // is the column null ? |
| if ((fieldStatus & |
| StoredFieldHeader.FIELD_NULL) == 0) |
| { |
| // the field is not null. |
| |
| // set the limit for the user read |
| if (!isOverflow) |
| { |
| // normal, non-overflow column case. |
| |
| dataIn.setLimit(fieldDataLength); |
| inUserCode = dataIn; |
| sColumn.readExternalFromArray(dataIn); |
| inUserCode = null; |
| int unread = dataIn.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(dataIn, unread); |
| } |
| else |
| { |
| // column being fetched is a long column. |
| |
| FormatIdInputStream newIn = |
| new FormatIdInputStream(overflowIn); |
| |
| // long columns are fetched as a stream. |
| |
| boolean fetchStream = true; |
| |
| if (!(sColumn instanceof StreamStorable)) |
| { |
| fetchStream = false; |
| } |
| |
| if (fetchStream) |
| { |
| ((StreamStorable) sColumn).setStream( |
| newIn); |
| } |
| else |
| { |
| inUserCode = newIn; |
| sColumn.readExternal(newIn); |
| inUserCode = null; |
| } |
| } |
| } |
| else |
| { |
| sColumn.restoreToNull(); |
| } |
| |
| } |
| else |
| { |
| |
| // At this point only non-Storable columns. |
| |
| if (StoredFieldHeader.isNull(fieldStatus)) |
| { |
| // Only Storables can be null ... |
| |
| throw StandardException.newException( |
| SQLState.DATA_NULL_STORABLE_COLUMN, |
| Integer.toString(columnId)); |
| } |
| |
| // This is a non-extensible field, which means the |
| // caller must know the correct type and thus the |
| // element in row is the correct type or null. It |
| // must be Serializable. |
| // |
| // We do not support Externalizable here. |
| |
| dataIn.setLimit(fieldDataLength); |
| inUserCode = dataIn; |
| // RESOLVE (no non-storables?) |
| row[columnId] = (Object) dataIn.readObject(); |
| inUserCode = null; |
| int unread = dataIn.clearLimit(); |
| if (unread != 0) |
| DataInputUtil.skipFully(dataIn, unread); |
| } |
| } |
| else |
| { |
| // column is non-existent. |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[columnId] = null; |
| } |
| } |
| |
| // move the counter to point to beginning of next field. |
| offset_to_field_data = dataIn.getPosition(); |
| } |
| else |
| { |
| // field is non-existent |
| Object column = row[columnId]; |
| |
| if (column instanceof DataValueDescriptor) |
| { |
| // RESOLVE - This is in place for 1.2. In the future |
| // we may want to return this column as non-existent |
| // even if it is a storable column, or maybe use a |
| // supplied default. |
| |
| ((DataValueDescriptor) column).restoreToNull(); |
| } |
| else |
| { |
| row[columnId] = null; |
| } |
| } |
| } |
| |
| // if the last column on this page is bigger than the highest |
| // column we are looking for, then we are done restoring the record. |
| |
| if ((numberFields + startColumn) > max_colid) |
| return true; |
| else |
| return false; |
| |
| } |
| catch (IOException ioe) |
| { |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| |
| if (inUserCode != null) |
| { |
| dataIn.clearLimit(); |
| |
| if (ioe instanceof EOFException) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.DEBUG_PRINT("DEBUG_TRACE", |
| "StoredPage - EOF while restoring record: " + |
| recordHeader + |
| "Page dump = " + this); |
| } |
| |
| // going beyond the limit in a DataInput class results in |
| // an EOFException when it sees the -1 from a read |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISMATCH, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // some SQLData error reporting |
| Exception ne = inUserCode.getNestedException(); |
| if (ne != null) |
| { |
| if (ne instanceof InstantiationException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_INSTANTIATION_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof IllegalAccessException) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SQLDATA_READ_ILLEGAL_ACCESS_EXCEPTION, |
| ne, inUserCode.getErrorInfo()); |
| } |
| |
| if (ne instanceof StandardException) |
| { |
| throw (StandardException) ne; |
| } |
| } |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| ioe, inUserCode.getErrorInfo()); |
| } |
| |
| // re-throw to higher levels so they can put it in correct context. |
| throw ioe; |
| |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| dataIn.clearLimit(); |
| |
| // an exception during the restore of a user column, this doesn't |
| // make the database corrupt, just that this field is inaccessable |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_MISSING_CLASS, |
| cnfe, inUserCode.getErrorInfo()); |
| |
| } |
| catch (LinkageError le) |
| { |
| // Some error during the link of a user class |
| if (inUserCode != null) |
| { |
| dataIn.clearLimit(); |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_READ_EXCEPTION, |
| le, inUserCode.getErrorInfo()); |
| } |
| throw le; |
| } |
| |
| } |
| |
| /** |
| * Restore a portion of a long column. |
| * <p> |
| * Restore a portion of a long column - user must supply two streams on top |
| * of the same data, one implements ObjectInput interface that knows how to |
| * restore the object, the other one implements LimitInputStream. |
| * |
| * @param fetchStream the stream to read the next portion of long col from |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public void restorePortionLongColumn( |
| OverflowInputStream fetchStream) |
| throws StandardException, IOException |
| { |
| int slot = |
| findRecordById(fetchStream.getOverflowId(), FIRST_SLOT_NUMBER); |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| int offset = getRecordOffset(slot); |
| int numberFields = recordHeader.getNumberFields(); |
| |
| if (SanityManager.DEBUG) |
| { |
| if ((numberFields > 2) || (numberFields < 1)) |
| { |
| SanityManager.THROWASSERT( |
| "longColumn record header must have 1 or 2 fields." + |
| "numberFields = " + numberFields); |
| } |
| } |
| |
| rawDataIn.setPosition(offset + recordHeader.size()); |
| |
| int fieldStatus = |
| StoredFieldHeader.readStatus(rawDataIn); |
| int fieldDataLength = |
| StoredFieldHeader.readFieldDataLength( |
| rawDataIn, fieldStatus, slotFieldSize); |
| |
| // read the data portion of this segment from the stream. |
| |
| ByteHolder bh = fetchStream.getByteHolder(); |
| bh.write(rawDataIn, fieldDataLength); |
| fetchStream.setByteHolder(bh); |
| |
| // set the next overflow pointer in the stream... |
| if (numberFields == 1) |
| { |
| // this is the last bit of the long column |
| fetchStream.setOverflowPage(-1); |
| fetchStream.setOverflowId(-1); |
| } |
| else |
| { |
| int firstFieldStatus = fieldStatus; // for DEBUG check |
| |
| // get the field status and data length of the overflow pointer. |
| fieldStatus = |
| StoredFieldHeader.readStatus(rawDataIn); |
| fieldDataLength = |
| StoredFieldHeader.readFieldDataLength( |
| rawDataIn, fieldStatus, slotFieldSize); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (!StoredFieldHeader.isOverflow(fieldStatus)) |
| { |
| // In version 1.5, the first field is overflow and the |
| // second is not. In version 2.0 onwards, the first |
| // field is not overflow and the second is overflow |
| // (the overflow bit goes with the overflow pointer). |
| // Check first field to make sure its overflow bit is |
| // set on. |
| SanityManager.ASSERT( |
| StoredFieldHeader.isOverflow(firstFieldStatus)); |
| } |
| } |
| |
| long overflowPage = |
| CompressedNumber.readLong((InputStream) rawDataIn); |
| int overflowId = |
| CompressedNumber.readInt((InputStream) rawDataIn); |
| |
| // there is more after this chunk. |
| fetchStream.setOverflowPage(overflowPage); |
| fetchStream.setOverflowId(overflowId); |
| } |
| } |
| |
| |
| /** |
| * Log a Storable to a stream. |
| * <p> |
| * Log a Storable into a stream. This is used by update field operations |
| * <P> |
| * Write the column in its field format to the stream. Field format is a |
| * field header followed the data of the column as defined by the data |
| * itself. See this class's description for the specifics of the header. |
| * |
| * @exception StandardException Standard Derby error policy |
| * @exception IOException RESOLVE |
| **/ |
| public void logColumn( |
| int slot, |
| int fieldId, |
| Object column, |
| DynamicByteArrayOutputStream out, |
| int overflowThreshold) |
| throws StandardException, IOException |
| { |
| // calculate the space available on the page, it includes |
| // the free space |
| // the space the record has reserved but not used |
| // the length of the old field itself |
| |
| // free space |
| int bytesAvailable = freeSpace; |
| int beginPosition = -1; |
| |
| // space reserved, but not used by the record |
| bytesAvailable += getReservedCount(slot); |
| |
| // The size of the old field is also available for the new field |
| rawDataIn.setPosition(getFieldOffset(slot, fieldId)); |
| |
| int fieldStatus = |
| StoredFieldHeader.readStatus(rawDataIn); |
| int fieldDataLength = |
| StoredFieldHeader.readFieldDataLength( |
| rawDataIn, fieldStatus, slotFieldSize); |
| |
| bytesAvailable += |
| StoredFieldHeader.size(fieldStatus, fieldDataLength, slotFieldSize) |
| + fieldDataLength; |
| |
| try |
| { |
| setOutputStream(out); |
| beginPosition = rawDataOut.getPosition(); |
| |
| Object[] row = new Object[1]; |
| row[0] = column; |
| if (bytesAvailable == logColumn( |
| row, 0, out, bytesAvailable, |
| COLUMN_NONE, overflowThreshold)) |
| { |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| } |
| finally |
| { |
| rawDataOut.setPosition(beginPosition); |
| resetOutputStream(); |
| } |
| } |
| |
| /** |
| * Log a long column into a DataOuput. |
| * <p> |
| * Log a long column into a DataOuput. This is used by insert operations |
| * <P> |
| * Write the column in its field format to the stream. Field format is a |
| * field header followed the data of the column as defined by the data |
| * itself. See this class's description for the specifics of the header. |
| * |
| * @param slot slot of the row with the column |
| * @param recordId record id of the |
| * @param column the object form of the column to log |
| * @param out where to log to the column to. |
| * |
| * @exception StandardException Standard Derby error policy |
| * @exception IOException I/O exception from writing to an array. |
| * |
| * @see BasePage#logColumn |
| **/ |
| public int logLongColumn( |
| int slot, |
| int recordId, |
| Object column, |
| DynamicByteArrayOutputStream out) |
| throws StandardException, IOException |
| { |
| int spaceAvailable = freeSpace; |
| |
| // need to account for the slot table using extra space... |
| spaceAvailable -= slotEntrySize; |
| |
| // <= is ok here as we know we want to write at least one more byte |
| if (spaceAvailable <= 0) |
| throw new NoSpaceOnPage(isOverflowPage()); |
| |
| setOutputStream(out); |
| int beginPosition = out.getPosition(); |
| |
| try |
| { |
| // in the long column portion on the new page there will be 1 field |
| // if the portion fits on the page (2 if it needs another pointer |
| // to continue to yet another page). |
| int numberFields = 1; |
| |
| StoredRecordHeader recordHeader = |
| new StoredRecordHeader(recordId, numberFields); |
| |
| int recordHeaderLength = recordHeader.write(logicalDataOut); |
| |
| spaceAvailable -= recordHeaderLength; |
| |
| if (spaceAvailable < 0) |
| { |
| // this part of long column won't totally fit on page, it |
| // needs to be linked to another page. Throw exception and |
| // caller will handle logging an overflow column portion |
| // with a forward pointer. |
| |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| else |
| { |
| // the rest of the long column fits on the page! |
| |
| Object[] row = new Object[1]; |
| row[0] = column; |
| return logColumn(row, 0, out, spaceAvailable, COLUMN_LONG, 100); |
| } |
| |
| } |
| finally |
| { |
| resetOutputStream(); |
| } |
| } |
| |
| /** |
| * Log column from input row to the given output stream. |
| * <p> |
| * Read data from row[arrayPosition], and write the column data in |
| * raw store page format to the given column. Along the way determine |
| * if the column will fit on the current page. |
| * <p> |
| * Action taken in this routine is determined by the kind of column as |
| * specified in the columnFlag: |
| * COLUMN_NONE - the column is insignificant |
| * COLUMN_FIRST - this is the first column in a logRow() call |
| * COLUMN_LONG - this is a known long column, therefore we will |
| * store part of the column on the current page and |
| * overflow the rest if necessary. |
| * <p> |
| * Upon entry to this routine logicalDataOut is tied to the |
| * DynamicByteArrayOutputStream out. |
| * <BR> |
| * If a column is a long column and it does not totally fit on the current |
| * page, then a LongColumnException is thrown. We package up info about |
| * the current long column in the partially filled in exception so that |
| * callers can take correct action. The column will now be set a as a |
| * stream. |
| * |
| * @return The spaceAvailable after accounting for space for this column. |
| * |
| * @param row array of column from which to read the column from. |
| * @param arrayPosition The array position of column to be reading from row. |
| * @param out The stream to write the raw store page format of the |
| * the column to. |
| * @param spaceAvailable The number of bytes available on the page for |
| * this column, this may differ from current page |
| * as it may include bytes used by previous |
| * columns. |
| * @param columnFlag one of: COLUMN_NONE, COLUMN_FIRST, or COLUMN_LONG. |
| * |
| * @exception StandardException Standard exception policy. |
| * @exception LongColumnException Thrown if column will not fit on a |
| * single page. See notes above |
| **/ |
| private int logColumn( |
| Object[] row, |
| int arrayPosition, |
| DynamicByteArrayOutputStream out, |
| int spaceAvailable, |
| int columnFlag, |
| int overflowThreshold) |
| throws StandardException, IOException |
| { |
| // RESOLVE (mikem) - why will row be null? |
| Object column = (row != null ? row[arrayPosition] : null); |
| |
| // Check to see if the data comes from a page, if it is, then the field |
| // header is already formatted. |
| if (column instanceof RawField) |
| { |
| // field data is raw, no need to set up a field header etc. |
| |
| byte[] data = ((RawField) column).getData(); |
| |
| if (data.length <= spaceAvailable) |
| { |
| out.write(data); |
| spaceAvailable -= data.length; |
| } |
| return spaceAvailable; |
| } |
| |
| // If this is a long column, it may fit in this page or it may not. |
| boolean longColumnDone = true; |
| |
| |
| // default field status. |
| int fieldStatus = |
| StoredFieldHeader.setFixed(StoredFieldHeader.setInitial(), true); |
| |
| int beginPosition = out.getPosition(); |
| int columnBeginPosition = 0; |
| int headerLength; |
| int fieldDataLength = 0; |
| |
| if (column instanceof StreamStorable) |
| { |
| StreamStorable stream_storable_column = (StreamStorable) column; |
| |
| if (stream_storable_column.returnStream() != null) |
| { |
| column = |
| (Object) stream_storable_column.returnStream(); |
| } |
| } |
| |
| if ( (column == null) && (columnFlag != COLUMN_CREATE_NULL)) |
| { |
| fieldStatus = StoredFieldHeader.setNonexistent(fieldStatus); |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| } |
| else if (column instanceof InputStream) |
| { |
| RememberBytesInputStream bufferedIn = null; |
| int bufferLen = 0; |
| |
| int estimatedMaxDataSize = |
| getMaxDataLength(spaceAvailable, overflowThreshold); |
| |
| // if column is already instanceof RememberBytesInputStream, then we |
| // need to find out how many bytes have already been stored in the |
| // buffer. |
| if (column instanceof RememberBytesInputStream) |
| { |
| // data is already RememberBytesInputStream |
| |
| bufferedIn = (RememberBytesInputStream) column; |
| bufferLen = bufferedIn.numBytesSaved(); |
| |
| } |
| else |
| { |
| // data comes in as an inputstream |
| bufferedIn = new RememberBytesInputStream( |
| (InputStream) column, new MemByteHolder(maxFieldSize + 1)); |
| |
| // always set stream of InputStream to RememberBytesInputStream |
| // so that all future access to this column will be able to |
| // get at the bytes drained from the InputStream, and copied |
| // into the RememberBytesInputStream. |
| if (row[arrayPosition] instanceof StreamStorable) |
| ((StreamStorable)row[arrayPosition]).setStream(bufferedIn); |
| |
| // set column to the RememberBytesInputStream so that |
| // all future access to this column will be able to get |
| // at bytes that have been already read. This assignment |
| // is needed to ensure that if long column exception is |
| // thrown, the column is set correctly |
| column = bufferedIn; |
| } |
| |
| // read the buffer by reading the max we can read. |
| if (bufferLen < (estimatedMaxDataSize + 1)) |
| { |
| bufferLen += |
| bufferedIn.fillBuf(estimatedMaxDataSize + 1 - bufferLen); |
| } |
| |
| if ((bufferLen <= estimatedMaxDataSize)) |
| { |
| // we will be able to fit this into the page |
| |
| fieldDataLength = bufferLen; |
| fieldStatus = StoredFieldHeader.setFixed(fieldStatus, true); |
| headerLength = StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| |
| // if the field is extensible, then we write the serializable |
| // formatId. if the field is non-extensible, we don't need to |
| // write the formatId. but at this point, how do we know |
| // whether the field is extensible or not??? For Plato release, |
| // we do not support InputStream on extensible types, |
| // therefore, we ignore the formatId for now. |
| bufferedIn.putBuf(logicalDataOut, fieldDataLength); |
| } |
| else |
| { |
| // current column will not fit into the current page. |
| |
| if (columnFlag == COLUMN_LONG) |
| { |
| // column is a long column and the remaining portion does |
| // not fit on the current page. |
| longColumnDone = false; |
| |
| // it's a portion of a long column, and there is more to |
| // write reserve enough room for overflow pointer, then |
| // write as much data as we can leaving an extra 2 bytes |
| // for overflow field header. |
| fieldDataLength = |
| estimatedMaxDataSize - OVERFLOW_POINTER_SIZE - 2; |
| fieldStatus = |
| StoredFieldHeader.setFixed(fieldStatus, true); |
| |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| bufferedIn.putBuf(logicalDataOut, fieldDataLength); |
| |
| // now, we need to adjust the buffer, move the unread |
| // bytes to the beginning position the cursor correctly, |
| // so, next time around, we can read more into the buffer. |
| int remainingBytes = bufferedIn.available(); |
| |
| // move the unread bytes to the beginning of the byteHolder. |
| int bytesShifted = bufferedIn.shiftToFront(); |
| |
| } |
| else |
| { |
| // column not a long column and does not fit on page. |
| int delta = maxFieldSize - bufferLen + 1; |
| |
| if (delta > 0) |
| bufferLen += bufferedIn.fillBuf(delta); |
| fieldDataLength = bufferLen; |
| |
| // the data will not fit on this page make sure the new |
| // input stream is passed back to the upper layer... |
| column = (Object) bufferedIn; |
| } |
| } |
| |
| } |
| else if ( columnFlag == COLUMN_CREATE_NULL ) |
| { |
| // |
| // This block handles the case when a couple columns have been added |
| // recently and now one of the later columns is being updated. Newly added columns |
| // which appear in the row before the updated column don't actually have |
| // any values yet. We stuff NULLs into those newly added columns here. |
| // This fixes DERBY-5679. |
| // |
| fieldStatus = StoredFieldHeader.setNull(fieldStatus, true); |
| |
| // header is written with 0 length here. |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| } |
| else if (column instanceof DataValueDescriptor) |
| { |
| DataValueDescriptor sColumn = (DataValueDescriptor) column; |
| |
| boolean isNull = (columnFlag == COLUMN_CREATE_NULL) || sColumn.isNull(); |
| if (isNull) |
| { |
| fieldStatus = StoredFieldHeader.setNull(fieldStatus, true); |
| } |
| |
| // header is written with 0 length here. |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| |
| if (!isNull) |
| { |
| // write the field data to the log |
| try |
| { |
| columnBeginPosition = out.getPosition(); |
| sColumn.writeExternal(logicalDataOut); |
| } |
| catch (IOException ioe) |
| { |
| // SQLData error reporting |
| if (logicalDataOut != null) |
| { |
| Exception ne = logicalDataOut.getNestedException(); |
| if (ne != null) |
| { |
| if (ne instanceof StandardException) |
| { |
| throw (StandardException) ne; |
| } |
| } |
| } |
| |
| |
| throw StandardException.newException( |
| SQLState.DATA_STORABLE_WRITE_EXCEPTION, ioe); |
| } |
| |
| fieldDataLength = |
| (out.getPosition() - beginPosition) - headerLength; |
| } |
| } |
| else if (column instanceof RecordHandle) |
| { |
| // we are inserting an overflow pointer for a long column |
| |
| // casted reference to column to avoid repeated casting. |
| RecordHandle overflowHandle = (RecordHandle) column; |
| |
| fieldStatus = StoredFieldHeader.setOverflow(fieldStatus, true); |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| |
| fieldDataLength += |
| CompressedNumber.writeLong(out, overflowHandle.getPageNumber()); |
| fieldDataLength += |
| CompressedNumber.writeInt(out, overflowHandle.getId()); |
| |
| } |
| else |
| { |
| // Serializable/Externalizable/Formattable |
| // all look the same at this point. |
| |
| // header is written with 0 length here. |
| headerLength = |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, |
| fieldDataLength, slotFieldSize); |
| |
| logicalDataOut.writeObject(column); |
| fieldDataLength = |
| (out.getPosition() - beginPosition) - headerLength; |
| } |
| |
| // calculate the size of the field on page with compresed field header |
| |
| fieldStatus = StoredFieldHeader.setFixed(fieldStatus, false); |
| int fieldSizeOnPage = |
| StoredFieldHeader.size(fieldStatus, fieldDataLength, slotFieldSize) |
| + fieldDataLength; |
| |
| userRowSize += fieldDataLength; |
| |
| boolean fieldIsLong = isLong(fieldSizeOnPage, overflowThreshold); |
| |
| // Do we have enough space on the page for this field? |
| if (((spaceAvailable < fieldSizeOnPage) || (fieldIsLong)) && |
| (columnFlag != COLUMN_LONG)) |
| { |
| // Column was not long before getting here and does not fit. |
| |
| if (fieldIsLong) |
| { |
| // long column, and this first time we have figured out this |
| // column is long. |
| |
| if (!(column instanceof InputStream)) |
| { |
| // Convert already written object to an InputStream. |
| ByteArray fieldData = |
| new ByteArray( |
| ((DynamicByteArrayOutputStream)out).getByteArray(), |
| (columnBeginPosition), fieldDataLength); |
| |
| ByteArrayInputStream columnIn = |
| new ByteArrayInputStream( |
| fieldData.getArray(), columnBeginPosition, |
| fieldDataLength); |
| |
| MemByteHolder byteHolder = |
| new MemByteHolder(fieldDataLength + 1); |
| |
| RememberBytesInputStream bufferedIn = |
| new RememberBytesInputStream(columnIn, byteHolder); |
| |
| // the data will not fit on this page make sure the new |
| // input stream is passed back to the upper layer... |
| column = bufferedIn; |
| } |
| |
| out.setPosition(beginPosition); |
| |
| // This exception carries the information for the client |
| // routine to continue inserting the long row on multiple |
| // pages. |
| LongColumnException lce = new LongColumnException(); |
| lce.setColumn(column); |
| throw lce; |
| |
| } |
| else |
| { |
| // Column does not fit on this page, but it isn't a long column. |
| |
| out.setPosition(beginPosition); |
| return(spaceAvailable); |
| } |
| } |
| |
| // Now we go back to update the fieldDataLength in the field header |
| out.setPosition(beginPosition); |
| |
| // slotFieldSize is set based on the pageSize. |
| // We are borrowing this to set the size of our fieldDataLength. |
| fieldStatus = StoredFieldHeader.setFixed(fieldStatus, true); |
| headerLength = StoredFieldHeader.write( |
| out, fieldStatus, fieldDataLength, slotFieldSize); |
| |
| // set position to the end of the field |
| out.setPosition(beginPosition + fieldDataLength + headerLength); |
| |
| spaceAvailable -= fieldSizeOnPage; |
| |
| // YYZ: revisit |
| if (columnFlag == COLUMN_LONG) |
| { |
| // if we are logging a long column, we don't care how much space |
| // is left on the page, instead, we care whether we are done with |
| // the column or not. So, here, we want to return 1. if we are |
| // not done, and return -1 if we are done. |
| // If logColumn returns -1, that flag is returned all the way to |
| // BasePage.insertLongColumn to signal end of loop. |
| if (longColumnDone) |
| return -1; |
| else |
| return 1; |
| } else |
| { |
| return (spaceAvailable); |
| } |
| } |
| |
| /** |
| * Create and write a long row header to the log stream. |
| * <p> |
| * Called to log a new overflow record, will check for space available |
| * and throw an exception if the record header will not fit on the page. |
| * <p> |
| * |
| * @return -1 |
| * |
| * @param slot slot of record to log. |
| * @param spaceAvailable spaceAvaliable on page. |
| * @param out stream to log the record to. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private int logOverflowRecord( |
| int slot, |
| int spaceAvailable, |
| DynamicByteArrayOutputStream out) |
| throws StandardException, IOException |
| { |
| setOutputStream(out); |
| |
| StoredRecordHeader pageRecordHeader = getHeaderAtSlot(slot); |
| |
| StoredRecordHeader overflow_rh = getOverFlowRecordHeader(); |
| overflow_rh.setOverflowFields(pageRecordHeader); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(overflow_rh.getOverflowPage() != 0); |
| } |
| |
| /* |
| // #1 situation, |
| // we want to update the header to just an overflow pointer with no data |
| // so, update the recordHeader, and we are done... |
| if (!overflow_rh.isPartialOverflow()) { |
| // this recordHeader becomes just a overflow pointer, |
| // we need to make sure that the number of fields is set to 0. |
| overflow_rh.setNumberFields(0); |
| |
| spaceAvailable -= overflow_rh.write(logicalDataOut); |
| |
| if (spaceAvailable < 0) { |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| |
| resetOutputStream(); |
| |
| return (-1); |
| } |
| */ |
| |
| // #2 situation, |
| // we want to only update the recordheader of the page, while leaving |
| // the data of the record on the page. Just update the header part and |
| // then arrange for the data part to move to after the new header. |
| |
| int oldSize = pageRecordHeader.size(); |
| int newSize = overflow_rh.size(); |
| |
| if (oldSize < newSize) |
| { |
| // need extra room... |
| int delta = newSize - oldSize; |
| if (spaceAvailable < delta) |
| { |
| throw new NoSpaceOnPage(isOverflowPage()); |
| } |
| } |
| |
| // write the new overflow_rh for the record. |
| overflow_rh.write(logicalDataOut); |
| |
| // now, log the data |
| logRecordDataPortion( |
| slot, LOG_RECORD_DEFAULT, pageRecordHeader, |
| (FormatableBitSet) null, logicalDataOut, (RecordHandle)null); |
| |
| return (-1); |
| } |
| |
| private int logOverflowField( |
| DynamicByteArrayOutputStream out, |
| int spaceAvailable, |
| long overflowPage, |
| int overflowId) |
| throws StandardException, IOException |
| { |
| int fieldStatus = |
| StoredFieldHeader.setOverflow( |
| StoredFieldHeader.setInitial(), true); |
| |
| int fieldSizeOnPage = |
| CompressedNumber.sizeLong(overflowPage) + |
| CompressedNumber.sizeInt(overflowId); |
| |
| int fieldDataLength = fieldSizeOnPage; |
| |
| fieldSizeOnPage += |
| StoredFieldHeader.size(fieldStatus, fieldDataLength, slotFieldSize); |
| |
| // need to check that we have room on the page for this. |
| spaceAvailable -= fieldSizeOnPage; |
| |
| // what if there is not enough room for the overflow pointer? |
| if (spaceAvailable < 0) |
| throw new NoSpaceOnPage(isOverflowPage()); |
| |
| // write the field to the page: |
| StoredFieldHeader.write( |
| logicalDataOut, fieldStatus, fieldDataLength, slotFieldSize); |
| CompressedNumber.writeLong(out, overflowPage); |
| CompressedNumber.writeInt(out, overflowId); |
| |
| // return the available bytes |
| return(spaceAvailable); |
| } |
| |
| /** |
| * Log a record to the ObjectOutput stream. |
| * <p> |
| * Write out the complete on-page record to the store stream. Data is |
| * preceeded by a compressed int that gives the length of the following |
| * data. |
| * |
| * @exception StandardException Standard Derby error policy |
| * @exception IOException on error writing to log stream. |
| * |
| * @see BasePage#logRecord |
| **/ |
| public void logRecord( |
| int slot, |
| int flag, |
| int recordId, |
| FormatableBitSet validColumns, |
| OutputStream out, |
| RecordHandle headRowHandle) |
| throws StandardException, IOException |
| { |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| if (recordId != recordHeader.getId()) |
| { |
| // the record is being logged under a different identifier, |
| // write it out with the correct identifier |
| StoredRecordHeader newRecordHeader = |
| new StoredRecordHeader(recordHeader); |
| |
| newRecordHeader.setId(recordId); |
| |
| newRecordHeader.write(out); |
| newRecordHeader = null; |
| } |
| else |
| { |
| // write the original record header |
| recordHeader.write(out); |
| } |
| |
| logRecordDataPortion( |
| slot, flag, recordHeader, validColumns, out, headRowHandle); |
| |
| } |
| |
| private void logRecordDataPortion( |
| int slot, |
| int flag, |
| StoredRecordHeader recordHeader, |
| FormatableBitSet validColumns, |
| OutputStream out, |
| RecordHandle headRowHandle) |
| throws StandardException, IOException |
| { |
| int offset = getRecordOffset(slot); |
| |
| // now skip over the original header before writing the data |
| int oldHeaderLength = recordHeader.size(); |
| offset += oldHeaderLength; |
| |
| // write out the record data (FH+data+...) from the page data |
| int startField = recordHeader.getFirstField(); |
| int endField = startField + recordHeader.getNumberFields(); |
| int validColumnsSize = (validColumns == null) ? 0 : validColumns.getLength(); |
| |
| for (int fieldId = startField; fieldId < endField; fieldId++) { |
| |
| rawDataIn.setPosition(offset); |
| |
| // get the field header information from the page |
| int fieldStatus = StoredFieldHeader.readStatus(rawDataIn); |
| int fieldDataLength = StoredFieldHeader.readFieldDataLength(rawDataIn, fieldStatus, slotFieldSize); |
| |
| // see if this field needs to be logged |
| // no need to write the data portion if the log is getting written |
| // for purges unless the field is overflow pointer for a long column. |
| if (((validColumns != null) && !(validColumnsSize > fieldId && validColumns.isSet(fieldId))) || |
| ((flag & BasePage.LOG_RECORD_FOR_PURGE)!=0 && !StoredFieldHeader.isOverflow(fieldStatus))) |
| { |
| // nope, move page offset along |
| offset += StoredFieldHeader.size(fieldStatus, fieldDataLength, slotFieldSize); |
| offset += fieldDataLength; |
| |
| // write a non-existent field |
| fieldStatus = StoredFieldHeader.setInitial(); |
| fieldStatus = StoredFieldHeader.setNonexistent(fieldStatus); |
| StoredFieldHeader.write(out, fieldStatus, 0, slotFieldSize); |
| continue; |
| } |
| |
| // If this field is to be updated, and it points to a long column |
| // chain, the entire long column chain will be orphaned after the |
| // update operation. Therefore, need to queue up a post commit |
| // work to reclaim the long column chain. We cannot do any clean |
| // up in this transaction now because we are underneath a log |
| // action and cannot interrupt the transaction log buffer. |
| // HeadRowHandle may be null if updateAtSlot is called to update a |
| // non-head row piece. In that case, don't do anything. |
| // If temp container, don't do anything. |
| if (((flag & BasePage.LOG_RECORD_FOR_UPDATE) != 0) && |
| headRowHandle != null && |
| StoredFieldHeader.isOverflow(fieldStatus) && |
| owner.isTemporaryContainer() == false) |
| { |
| |
| int saveOffset = rawDataIn.getPosition(); // remember the page offset |
| long overflowPage = CompressedNumber.readLong((InputStream) rawDataIn); |
| int overflowId = CompressedNumber.readInt((InputStream) rawDataIn); |
| |
| // Remember the time stamp on the first page of the column |
| // chain. This is to prevent the case where the post commit |
| // work gets fired twice, in that case, the second time it is |
| // fired, this overflow page may not part of this row chain |
| // that is being updated. |
| Page firstPageOnColumnChain = getOverflowPage(overflowPage); |
| PageTimeStamp ts = firstPageOnColumnChain.currentTimeStamp(); |
| firstPageOnColumnChain.unlatch(); |
| |
| RawTransaction rxact = (RawTransaction)owner.getTransaction(); |
| |
| ReclaimSpace work = |
| new ReclaimSpace(ReclaimSpace.COLUMN_CHAIN, |
| headRowHandle, |
| fieldId, // long column about to be orphaned by update |
| overflowPage, // page where the long column starts |
| overflowId, // record Id of the beginning of the long column |
| ts, |
| rxact.getDataFactory(), true); |
| |
| rxact.addPostCommitWork(work); |
| |
| rawDataIn.setPosition(saveOffset); // Just to be safe, reset data stream |
| } |
| |
| |
| // write the field header for the log |
| offset += StoredFieldHeader.write(out, fieldStatus, fieldDataLength, slotFieldSize); |
| |
| if (fieldDataLength != 0) { |
| |
| // write the actual data |
| out.write(pageData, offset, fieldDataLength); |
| |
| offset += fieldDataLength; |
| } |
| } |
| } |
| |
| /** |
| Log a field to the ObjectOutput stream. |
| <P> |
| Find the field in the record and then write out the complete |
| field, i.e. header and data. |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| |
| @see BasePage#logField |
| */ |
| |
| public void logField(int slot, int fieldNumber, OutputStream out) |
| throws StandardException, IOException |
| { |
| int offset = getFieldOffset(slot, fieldNumber); |
| |
| // these reads are always against the page array |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // now write out the field we are interested in ... |
| lrdi.setPosition(offset); |
| int fieldStatus = StoredFieldHeader.readStatus(lrdi); |
| int fieldDataLength = StoredFieldHeader.readFieldDataLength(lrdi, fieldStatus, slotFieldSize); |
| |
| StoredFieldHeader.write(out, fieldStatus, fieldDataLength, slotFieldSize); |
| |
| if (fieldDataLength != 0) { |
| // and then the data |
| out.write(pageData, lrdi.getPosition(), fieldDataLength); |
| } |
| } |
| |
| /* |
| ** Overidden methods of BasePage |
| */ |
| |
| /** |
| Override insertAtSlot to provide long row support. |
| @exception StandardException Standard Derby error policy |
| */ |
| public RecordHandle insertAtSlot( |
| int slot, |
| Object[] row, |
| FormatableBitSet validColumns, |
| LogicalUndo undo, |
| byte insertFlag, |
| int overflowThreshold) |
| throws StandardException |
| { |
| try { |
| |
| return super.insertAtSlot(slot, row, validColumns, undo, insertFlag, overflowThreshold); |
| |
| } catch (NoSpaceOnPage nsop) { |
| |
| // Super class already handle the case of insert that allows overflow. |
| // If we get here, we know that the insert should not allow overflow. |
| // Possibles causes: |
| // 1. insert to an empty page, row will never fit (ie long row) |
| // 2. insert to original page |
| // we will do: |
| // return a null to indicate the insert cannot be accepted .. |
| return null; |
| |
| } |
| } |
| |
| |
| /** |
| Update field at specified slot |
| @exception StandardException Standard Derby error policy |
| */ |
| public RecordHandle updateFieldAtSlot( |
| int slot, |
| int fieldId, |
| Object newValue, |
| LogicalUndo undo) |
| throws StandardException |
| { |
| try { |
| |
| return super.updateFieldAtSlot(slot, fieldId, newValue, undo); |
| |
| } catch (NoSpaceOnPage nsop) { |
| |
| |
| // empty page apart from the record |
| if (slotsInUse == 1) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_NO_SPACE_FOR_RECORD); |
| } |
| throw StandardException.newException( |
| SQLState.DATA_NO_SPACE_FOR_RECORD); |
| |
| /* |
| // djd if (isOverflowPage()) { |
| } |
| |
| return XXX; |
| */ |
| } |
| |
| } |
| |
| /** |
| Get the number of fields on the row at slot |
| @exception StandardException Standard Derby error policy |
| */ |
| public int fetchNumFieldsAtSlot(int slot) throws StandardException |
| { |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| if (!recordHeader.hasOverflow()) |
| return super.fetchNumFieldsAtSlot(slot); |
| |
| BasePage overflowPage = getOverflowPage(recordHeader.getOverflowPage()); |
| int count = overflowPage.fetchNumFieldsAtSlot(getOverflowSlot(overflowPage, recordHeader)); |
| overflowPage.unlatch(); |
| return count; |
| } |
| |
| /** |
| * Move record to a page toward the beginning of the file. |
| * <p> |
| * As part of compressing the table records need to be moved from the |
| * end of the file toward the beginning of the file. Only the |
| * contiguous set of free pages at the very end of the file can |
| * be given back to the OS. This call is used to purge the row from |
| * the current page, insert it into a previous page, and return the |
| * new row location |
| * Mark the record identified by position as deleted. The record may be |
| * undeleted sometime later using undelete() by any transaction that sees |
| * the record. |
| * <p> |
| * The interface is optimized to work on a number of rows at a time, |
| * optimally processing all rows on the page at once. The call will |
| * process either all rows on the page, or the number of slots in the |
| * input arrays - whichever is smaller. |
| * <B>Locking Policy</B> |
| * <P> |
| * MUST be called with table locked, no locks are requested. Because |
| * it is called with table locks the call will go ahead and purge any |
| * row which is marked deleted. It will also use purge rather than |
| * delete to remove the old row after it moves it to a new page. This |
| * is ok since the table lock insures that no other transaction will |
| * use space on the table before this transaction commits. |
| * |
| * <BR> |
| * A page latch on the new page will be requested and released. |
| * |
| * @param slot slot of original row to move. |
| * @param row a row template to hold all columns of row. |
| * @param old_handle An array to be filled in by the call with the |
| * old handles of all rows moved. |
| * @param new_handle An array to be filled in by the call with the |
| * new handles of all rows moved. |
| * |
| * @return the number of rows processed. |
| * |
| * @exception StandardException Standard Derby error policy |
| * |
| **/ |
| public int moveRecordForCompressAtSlot( |
| int slot, |
| Object[] row, |
| RecordHandle[] old_handle, |
| RecordHandle[] new_handle) |
| throws StandardException |
| { |
| long src_pageno = getPageNumber(); |
| |
| try |
| { |
| fetchFromSlot( |
| null, |
| slot, |
| row, |
| (FetchDescriptor) null, // all columns retrieved |
| false); |
| |
| int row_size = getRecordPortionLength(slot); |
| int record_id = getHeaderAtSlot(slot).getId(); |
| |
| // first see if row will fit on current page being used to insert |
| StoredPage dest_page = |
| (StoredPage) owner.getPageForCompress(0, src_pageno); |
| |
| if (dest_page != null) |
| { |
| if ((dest_page.getPageNumber() >= getPageNumber()) || |
| (!dest_page.spaceForCopy(row_size, record_id))) |
| { |
| // page won't work |
| dest_page.unlatch(); |
| dest_page = null; |
| } |
| } |
| |
| if (dest_page == null) |
| { |
| // last page did not work, try unfilled page |
| dest_page = (StoredPage) |
| owner.getPageForCompress( |
| ContainerHandle.GET_PAGE_UNFILLED, src_pageno); |
| |
| if (dest_page != null) |
| { |
| if ((dest_page.getPageNumber() >= getPageNumber()) || |
| (!dest_page.spaceForCopy(row_size, record_id))) |
| { |
| // page won't work |
| dest_page.unlatch(); |
| dest_page = null; |
| } |
| } |
| } |
| |
| if (dest_page == null) |
| { |
| // last and unfilled page did not work, try getting a free page |
| dest_page = (StoredPage) owner.addPage(); |
| |
| if ((dest_page.getPageNumber() >= getPageNumber()) || |
| (!dest_page.spaceForCopy(row_size, record_id))) |
| { |
| // The only time a new page might not have enough space is |
| // if the source row fills or almost fills a page by itself |
| // and has a record id that is smaller than the record id |
| // will be on the destination page such that the increase |
| // in space. Record id's are stored on the page in a |
| // compressed format such that depending on the value they |
| // may store in 1, 2, or 4 bytes, thus the destination page |
| // may need an additional 1, 2 or 3 bytes |
| // depending on the source and destination row id's. |
| // Because of record header overhead this can only happen |
| // if there is just one row on a page. For now just going |
| // to give up on moving this row. Future work could |
| // improve the algorithm to find a page with an equal or |
| // smaller stored record id in this case. |
| |
| owner.removePage(dest_page); |
| dest_page = null; |
| } |
| } |
| |
| if (dest_page != null) |
| { |
| int dest_slot = dest_page.recordCount(); |
| |
| old_handle[0] = getRecordHandleAtSlot(slot); |
| |
| copyAndPurge(dest_page, slot, 1, dest_slot); |
| |
| new_handle[0] = dest_page.getRecordHandleAtSlot(dest_slot); |
| |
| dest_page.unlatch(); |
| |
| return(1); |
| } |
| else |
| { |
| return(0); |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| } |
| |
| /* |
| * methods that is called underneath a page action |
| */ |
| |
| /* |
| * update page version and instance due to actions by a log record |
| */ |
| public void logAction(LogInstant instant) throws StandardException |
| { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(isLatched(), |
| "logAction() executed on an unlatched page."); |
| } |
| |
| if (rawDataOut == null) |
| createOutStreams(); |
| |
| if (!isActuallyDirty()) { |
| // if this is not an overflow page and the page is valid, set the |
| // initial row count. |
| if (!isOverflowPage() && ((getPageStatus() & VALID_PAGE) != 0)) { |
| initialRowCount = internalNonDeletedRecordCount(); |
| } else |
| initialRowCount = 0; |
| } |
| |
| setDirty(); |
| |
| bumpPageVersion(); |
| updateLastLogInstant(instant); |
| } |
| |
| |
| /* clean the page for first use or reuse */ |
| private void cleanPage() |
| { |
| setDirty(); |
| |
| // set pageData to all nulls |
| clearSection(0, getPageSize()); |
| |
| slotsInUse = 0; |
| deletedRowCount = 0; |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty maybe called unlatched |
| |
| clearAllSpace(); |
| |
| } |
| |
| /** |
| Initialize the page. |
| |
| If reuse, then |
| Clean up any in memory or on disk structure to ready the page for reuse. |
| This is not only reusing the page buffer, but reusing a free page |
| which may or may not be cleaned up the the client of raw store when it |
| was deallocated. |
| |
| @exception StandardException Derby Standard Error Policy |
| */ |
| public void initPage(LogInstant instant, byte status, int recordId, |
| boolean overflow, boolean reuse) |
| throws StandardException |
| { |
| // log action at the end after the page is updated with all the |
| // pertinent information |
| logAction(instant); |
| |
| if (reuse) |
| { |
| cleanPage(); |
| super.cleanPageForReuse(); |
| } |
| // if not reuse, createPage already called cleanpage |
| |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty maybe called unlatched |
| setPageStatus(status); |
| isOverflowPage = overflow; |
| nextId = recordId; |
| |
| } |
| |
| /** |
| Set page status |
| @exception StandardException Derby Standard Error Policy |
| */ |
| public void setPageStatus(LogInstant instant, byte status) |
| throws StandardException |
| { |
| logAction(instant); |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty maybe called unlatched |
| |
| setPageStatus(status); |
| } |
| |
| /** |
| Set the row reserved space. |
| @exception StandardException Derby Standard Error Policy |
| */ |
| public void setReservedSpace(LogInstant instant, int slot, int value) |
| throws StandardException, IOException |
| { |
| logAction(instant); |
| headerOutOfDate = true; // headerOutOfDate must be set after setDirty |
| // because isDirty maybe called unlatched |
| |
| int delta = value - getReservedCount(slot); |
| |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(delta <= freeSpace, |
| "Cannot grow reserved space because there is not enough free space on the page"); |
| SanityManager.ASSERT(delta != 0, |
| "Set Reserved Space called to set identical value"); |
| |
| if (value < 0) |
| SanityManager.THROWASSERT( |
| "Cannot set reserved space to value " + value); |
| } |
| |
| // Find the end of the record that we are about to add or subtract from |
| // the reserved space. |
| int nextRecordOffset = getRecordOffset(slot) + getTotalSpace(slot); |
| |
| if (delta > 0) { |
| // Growing - hopefully during a RRR restore |
| expandPage(nextRecordOffset, delta); |
| } else { |
| // shrinking, delta is < 0 |
| shrinkPage(nextRecordOffset, -delta); |
| } |
| |
| // Lastly, update the reserved space count in the slot. |
| rawDataOut.setPosition(getSlotOffset(slot) + (2*slotFieldSize)); |
| if (slotFieldSize == SMALL_SLOT_SIZE) |
| logicalDataOut.writeShort(value); |
| else |
| logicalDataOut.writeInt(value); |
| |
| } |
| |
| |
| /** |
| Store a record at the given slot. |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| */ |
| public void storeRecord(LogInstant instant, int slot, boolean insert, ObjectInput in) |
| throws StandardException, IOException |
| { |
| logAction(instant); |
| |
| if (insert) |
| storeRecordForInsert(slot, in); |
| else |
| storeRecordForUpdate(slot, in); |
| } |
| |
| private void storeRecordForInsert(int slot, ObjectInput in) |
| throws StandardException, IOException |
| { |
| |
| StoredRecordHeader recordHeader = shiftUp(slot); |
| if (recordHeader == null) { |
| recordHeader = new StoredRecordHeader(); |
| setHeaderAtSlot(slot, recordHeader); |
| } |
| |
| bumpRecordCount(1); |
| |
| // recordHeader represents the new version of the record header. |
| recordHeader.read(in); |
| |
| // the record is already marked delete, we need to bump the deletedRowCount |
| if (recordHeader.isDeleted()) { |
| deletedRowCount++; |
| headerOutOfDate = true; |
| } |
| |
| // during a rollforward insert, recordId == nextId |
| // during a rollback of purge, recordId < nextId |
| if (nextId <= recordHeader.getId()) |
| nextId = recordHeader.getId()+1; |
| |
| int recordOffset = firstFreeByte; |
| int offset = recordOffset; |
| |
| // write each field out to the page |
| int numberFields = recordHeader.getNumberFields(); |
| |
| rawDataOut.setPosition(offset); |
| offset += recordHeader.write(rawDataOut); |
| |
| int userData = 0; |
| for (int i = 0; i < numberFields; i++) { |
| |
| // get the field header information, the input stream came from the log |
| int newFieldStatus = StoredFieldHeader.readStatus(in); |
| int newFieldDataLength = StoredFieldHeader.readFieldDataLength(in, newFieldStatus, slotFieldSize); |
| newFieldStatus = StoredFieldHeader.setFixed(newFieldStatus, false); |
| |
| rawDataOut.setPosition(offset); |
| offset += StoredFieldHeader.write(rawDataOut, newFieldStatus, newFieldDataLength, slotFieldSize); |
| |
| if (newFieldDataLength != 0) { |
| in.readFully(pageData, offset, newFieldDataLength); |
| offset += newFieldDataLength; |
| userData += newFieldDataLength; |
| } |
| } |
| |
| int dataWritten = offset - firstFreeByte; |
| |
| freeSpace -= dataWritten; |
| firstFreeByte += dataWritten; |
| |
| int reservedSpace = 0; |
| if (minimumRecordSize > 0) { |
| |
| // make sure we reserve the minimumRecordSize for the user data |
| // portion of the record excluding the space we took on recordHeader |
| // and fieldHeaders. |
| if (userData < minimumRecordSize) { |
| reservedSpace = minimumRecordSize - userData; |
| freeSpace -= reservedSpace; |
| firstFreeByte += reservedSpace; |
| } |
| } |
| |
| if (isOverflowPage()) |
| { |
| // The total length of the row including the row header, field |
| // headers, user data, and unused reserve space must be at least |
| // as big as the worst case overflow row pointer. This is so that |
| // it always possible to do an expanding update on a row piece that |
| // in the worst case results in just using the existing space to |
| // put in an overflow pointer to another row segment on some other |
| // page. |
| int additional_space_needed = |
| StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE - |
| (dataWritten + reservedSpace); |
| |
| if (additional_space_needed > 0) |
| { |
| // need to reserve more space for the row to handle worst case |
| // update of the row to an overflow row piece. |
| freeSpace -= additional_space_needed; |
| firstFreeByte += additional_space_needed; |
| reservedSpace += additional_space_needed; |
| } |
| } |
| |
| // update the slot table |
| addSlotEntry(slot, recordOffset, dataWritten, reservedSpace); |
| |
| if (SanityManager.DEBUG) |
| { |
| if ((freeSpace < 0) || |
| (firstFreeByte > getSlotOffset(slotsInUse - 1)) || |
| ((firstFreeByte + freeSpace) != getSlotOffset(slotsInUse - 1))) |
| { |
| SanityManager.THROWASSERT( |
| " inconsistency in space management during insert: " + |
| " slot = " + slot + |
| " getSlotOffset(slot) = " + getSlotOffset(slot) + |
| " dataWritten = " + dataWritten + |
| " freeSpace = " + freeSpace + |
| " firstFreeByte = " + firstFreeByte + |
| " page = " + this); |
| } |
| } |
| |
| if ((firstFreeByte > getSlotOffset(slot)) || (freeSpace < 0)) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, getPageId())); |
| } |
| |
| } |
| |
| |
| private void storeRecordForUpdate(int slot, ObjectInput in) |
| throws StandardException, IOException |
| { |
| // set up to read the in-memory record header back from the record |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| StoredRecordHeader newRecorderHeader = new StoredRecordHeader(); |
| |
| // recordHeader represents the new version of the record header. |
| newRecorderHeader.read(in); |
| |
| int oldFieldCount = recordHeader.getNumberFields(); |
| int newFieldCount = newRecorderHeader.getNumberFields(); |
| |
| int startField = recordHeader.getFirstField(); |
| if (SanityManager.DEBUG) { |
| if (startField != newRecorderHeader.getFirstField()) |
| SanityManager.THROWASSERT("First field changed from " + startField + " to " + newRecorderHeader.getFirstField()); |
| } |
| |
| // See if the number of fields shrunk, if so clear out the old data |
| // we do this first to stop shuffling about the fields that are going to |
| // be deleted during the update of the earlier fields. This case occurs |
| // on an update that changes the row to be overflowed. |
| if (newFieldCount < oldFieldCount) { |
| |
| int oldDataStartingOffset = getFieldOffset(slot, startField + newFieldCount); |
| |
| // calculate the length of the to be deleted fields |
| int deleteLength = getRecordOffset(slot) + getRecordPortionLength(slot) - oldDataStartingOffset; |
| |
| // we are updateing to zero bytes! |
| updateRecordPortionLength(slot, -(deleteLength), deleteLength); |
| } |
| |
| // write each field out to the page |
| |
| int startingOffset = getRecordOffset(slot); |
| int newOffset = startingOffset; |
| int oldOffset = startingOffset; |
| |
| // see which field we get to use the reserve space |
| int reservedSpaceFieldId = newFieldCount < oldFieldCount ? |
| newFieldCount - 1 : oldFieldCount - 1; |
| reservedSpaceFieldId += startField; |
| |
| |
| // the new data the needs to be written at newOffset but can't until |
| // unsedSpace >= newDataToWrite.length (allowing for the header) |
| DynamicByteArrayOutputStream newDataToWrite = null; |
| |
| rawDataOut.setPosition(newOffset); |
| |
| // write the record header, which may change in size |
| int oldLength = recordHeader.size(); |
| int newLength = newRecorderHeader.size(); |
| |
| int unusedSpace = oldLength; // the unused space at newOffset |
| |
| // no fields, so we can eat into the reserve space |
| if (reservedSpaceFieldId < startField) // no fields |
| unusedSpace += getReservedCount(slot); |
| |
| if (unusedSpace >= newLength) { |
| newRecorderHeader.write(rawDataOut); |
| newOffset += newLength; |
| unusedSpace -= newLength; |
| |
| } else { |
| |
| newDataToWrite = new DynamicByteArrayOutputStream(getPageSize()); |
| newRecorderHeader.write(newDataToWrite); |
| } |
| oldOffset += oldLength; |
| int recordDelta = (newLength - oldLength); |
| |
| int oldFieldStatus = 0; |
| int oldFieldDataLength = 0; |
| int newFieldStatus = 0; |
| int newFieldDataLength = 0; |
| |
| int oldEndFieldExclusive = startField + oldFieldCount; |
| int newEndFieldExclusive = startField + newFieldCount; |
| |
| for (int fieldId = startField; fieldId < newEndFieldExclusive; fieldId++) { |
| |
| int oldFieldLength = 0; |
| if (fieldId < oldEndFieldExclusive) { |
| rawDataIn.setPosition(oldOffset); |
| oldFieldStatus = StoredFieldHeader.readStatus(rawDataIn); |
| oldFieldDataLength = StoredFieldHeader.readFieldDataLength(rawDataIn, oldFieldStatus, slotFieldSize); |
| oldFieldLength = StoredFieldHeader.size(oldFieldStatus, oldFieldDataLength, slotFieldSize) |
| + oldFieldDataLength; |
| } |
| |
| newFieldStatus = StoredFieldHeader.readStatus(in); |
| newFieldDataLength = StoredFieldHeader.readFieldDataLength(in, newFieldStatus, slotFieldSize); |
| |
| // if no value was provided on an update of a field then use the old value, |
| // unless the old field didn't exist. |
| if (StoredFieldHeader.isNonexistent(newFieldStatus) && (fieldId < oldEndFieldExclusive)) { |
| |
| // may need to move this old field ... |
| if ((newDataToWrite == null) || (newDataToWrite.getUsed() == 0)) { |
| // the is no old data to catch up on, is the data at |
| // the correct position already? |
| if (newOffset == oldOffset) { |
| // yes, nothing to do!! |
| if (SanityManager.DEBUG) { |
| if (unusedSpace != 0) |
| SanityManager.THROWASSERT("Unused space is out of sync, expect 0 got " + unusedSpace); |
| } |
| } else { |
| // need to shift the field left |
| if (SanityManager.DEBUG) { |
| if (unusedSpace != (oldOffset - newOffset)) |
| SanityManager.THROWASSERT( |
| "Unused space is out of sync expected " + (oldOffset - newOffset) + " got " + unusedSpace); |
| } |
| |
| System.arraycopy(pageData, oldOffset, pageData, newOffset, oldFieldLength); |
| } |
| newOffset += oldFieldLength; |
| |
| // last field to be updated can eat into the reserve space |
| if (fieldId == reservedSpaceFieldId) |
| unusedSpace += getReservedCount(slot); |
| |
| } else { |
| // there is data still to be written, just append this field to the |
| // saved data |
| int position = newDataToWrite.getPosition(); |
| newDataToWrite.setPosition(position + oldFieldLength); |
| System.arraycopy(pageData, oldOffset, |
| newDataToWrite.getByteArray(), position, oldFieldLength); |
| |
| unusedSpace += oldFieldLength; |
| |
| // last field to be updated can eat into the reserve space |
| if (fieldId == reservedSpaceFieldId) |
| unusedSpace += getReservedCount(slot); |
| |
| // attempt to write out some of what we have in the side buffer now. |
| int copyLength = moveSavedDataToPage(newDataToWrite, unusedSpace, newOffset); |
| newOffset += copyLength; |
| unusedSpace -= copyLength; |
| |
| } |
| oldOffset += oldFieldLength; |
| continue; |
| } |
| |
| newFieldStatus = StoredFieldHeader.setFixed(newFieldStatus, false); |
| |
| int newFieldHeaderLength = StoredFieldHeader.size(newFieldStatus, newFieldDataLength, slotFieldSize); |
| int newFieldLength = newFieldHeaderLength + newFieldDataLength; |
| |
| recordDelta += (newFieldLength - oldFieldLength); |
| |
| // See if we can write this field now |
| |
| // space available increases by the amount of the old field |
| unusedSpace += oldFieldLength; |
| oldOffset += oldFieldLength; |
| |
| // last field to be updated can eat into the reserve space |
| if (fieldId == reservedSpaceFieldId) |
| unusedSpace += getReservedCount(slot); |
| |
| if ((newDataToWrite != null) && (newDataToWrite.getUsed() != 0)) { |
| |
| // catch up on the old data if possible |
| int copyLength = moveSavedDataToPage(newDataToWrite, unusedSpace, newOffset); |
| newOffset += copyLength; |
| unusedSpace -= copyLength; |
| } |
| |
| if (((newDataToWrite == null) || (newDataToWrite.getUsed() == 0)) |
| && (unusedSpace >= newFieldHeaderLength)) { |
| |
| // can fit the header in |
| rawDataOut.setPosition(newOffset); |
| newOffset += StoredFieldHeader.write(rawDataOut, newFieldStatus, newFieldDataLength, slotFieldSize); |
| unusedSpace -= newFieldHeaderLength; |
| |
| if (newFieldDataLength != 0) { |
| |
| // read as much as the field as possible |
| int fieldCopy = unusedSpace >= newFieldDataLength ? |
| newFieldDataLength : unusedSpace; |
| |
| if (fieldCopy != 0) { |
| in.readFully(pageData, newOffset, fieldCopy); |
| |
| newOffset += fieldCopy; |
| unusedSpace -= fieldCopy; |
| } |
| |
| |
| fieldCopy = newFieldDataLength - fieldCopy; |
| if (fieldCopy != 0) { |
| if (newDataToWrite == null) |
| newDataToWrite = new DynamicByteArrayOutputStream(newFieldLength * 2); |
| |
| // append the remaining portion of the field to the saved data |
| int position = newDataToWrite.getPosition(); |
| newDataToWrite.setPosition(position + fieldCopy); |
| in.readFully(newDataToWrite.getByteArray(), |
| position, fieldCopy); |
| |
| } |
| } |
| } else { |
| // can't fit these header, or therefore the field, append it |
| // to the buffer. |
| |
| if (newDataToWrite == null) |
| newDataToWrite = new DynamicByteArrayOutputStream(newFieldLength * 2); |
| |
| StoredFieldHeader.write(newDataToWrite, newFieldStatus, newFieldDataLength, slotFieldSize); |
| |
| // save the new field data |
| if (newFieldDataLength != 0) { |
| int position = newDataToWrite.getPosition(); |
| newDataToWrite.setPosition(position + newFieldDataLength); |
| in.readFully(newDataToWrite.getByteArray(), |
| position, newFieldDataLength); |
| } |
| } |
| } |
| |
| // at this point there may still be data left in the saved buffer |
| // but presumably we can't fit it in |
| |
| int reservedDelta; |
| |
| if ((newDataToWrite != null) && (newDataToWrite.getUsed() != 0)) { |
| |
| // need to shift the later records down ... |
| int nextRecordOffset = startingOffset + getTotalSpace(slot); |
| |
| int spaceRequiredFromFreeSpace = newDataToWrite.getUsed() - (nextRecordOffset - newOffset); |
| |
| if (SanityManager.DEBUG) { |
| if (newOffset > nextRecordOffset) |
| SanityManager.THROWASSERT("data has overwritten next record - offset " + newOffset |
| + " next record " + nextRecordOffset); |
| |
| if ((spaceRequiredFromFreeSpace <= 0) || (spaceRequiredFromFreeSpace > freeSpace)) |
| SanityManager.THROWASSERT("invalid space required " + spaceRequiredFromFreeSpace |
| + " newDataToWrite.getUsed() " + newDataToWrite.getUsed() |
| + " nextRecordOffset " + nextRecordOffset |
| + " newOffset " + newOffset |
| + " reservedSpaceFieldId " + reservedSpaceFieldId |
| + " startField " + startField |
| + " newEndFieldExclusive " + newEndFieldExclusive |
| + " newFieldCount " + newFieldCount |
| + " oldFieldCount " + oldFieldCount |
| + " slot " + slot |
| + " freeSpace " + freeSpace |
| + " unusedSpace " + unusedSpace |
| + " page " + getPageId()); |
| |
| |
| if ((getReservedCount(slot) + spaceRequiredFromFreeSpace) != recordDelta) |
| SanityManager.THROWASSERT("mismatch on count: reserved " + getReservedCount(slot) + |
| "free space take " + spaceRequiredFromFreeSpace + |
| "record delta " + recordDelta); |
| |
| } |
| |
| if (spaceRequiredFromFreeSpace > freeSpace) { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, getPageId())); |
| } |
| |
| // see if this is the last record on the page, if so a simple |
| // shift of the remaining fields will sufice... |
| expandPage(nextRecordOffset, spaceRequiredFromFreeSpace); |
| |
| unusedSpace += spaceRequiredFromFreeSpace; |
| |
| moveSavedDataToPage(newDataToWrite, unusedSpace, newOffset); |
| |
| reservedDelta = -1 * getReservedCount(slot); |
| |
| if (SanityManager.DEBUG) { |
| if (newDataToWrite.getUsed() != 0) |
| SanityManager.THROWASSERT("data is left in save buffer ... " + newDataToWrite.getUsed()); |
| } |
| } else { |
| reservedDelta = -1 * recordDelta; |
| } |
| |
| // now reset the length in the slot entry |
| updateRecordPortionLength(slot, recordDelta, reservedDelta); |
| |
| setHeaderAtSlot(slot, newRecorderHeader); |
| } |
| |
| private int moveSavedDataToPage(DynamicByteArrayOutputStream savedData, int unusedSpace, int pageOffset) { |
| // catch up on the old data if possible |
| if (unusedSpace > (savedData.getUsed() / 2)) { |
| // copy onto the page |
| int copyLength = unusedSpace <= savedData.getUsed() ? |
| unusedSpace : savedData.getUsed(); |
| System.arraycopy(savedData.getByteArray(), 0, |
| pageData, pageOffset, copyLength); |
| |
| // fix up the saved buffer |
| savedData.discardLeft(copyLength); |
| |
| return copyLength; |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| Create the space to update a portion of a record. |
| This method ensures there is enough room to replace the |
| old data of length oldLength at the given offset, with the new data of length |
| newLength. This method does put any new data on the page, it moves old data around |
| and zeros out any old data when newLength < oldLength. This method does |
| update the information in the slot table. |
| |
| The passed in offset is the correct place to put the data |
| when this method returns, ie. it only moves data that |
| has an offset greater then this. |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| */ |
| private void createSpaceForUpdate(int slot, int offset, int oldLength, int newLength) |
| throws StandardException, IOException |
| { |
| |
| // now replace the old data with the new data |
| if (newLength <= oldLength) { |
| |
| // now shift the remaining data down ... |
| int diffLength = oldLength - newLength; |
| |
| // real easy |
| if (diffLength == 0) |
| return; |
| |
| // shift the remaing fields down |
| int remainingLength = |
| shiftRemainingData(slot, offset, oldLength, newLength); |
| |
| // clear the now unused data on the page |
| clearSection(offset + newLength + remainingLength, diffLength); |
| |
| if (SanityManager.DEBUG) { |
| |
| if ((getRecordPortionLength(slot) - diffLength) != |
| ((offset - getRecordOffset(slot)) + newLength + |
| remainingLength)) |
| { |
| SanityManager.THROWASSERT( |
| " Slot table trying to update record length " + |
| (getRecordPortionLength(slot) - diffLength) + |
| " that is not the same as what it actully is"); |
| } |
| } |
| |
| // now reset the length in the slot entry, increase the reserved space |
| updateRecordPortionLength(slot, -(diffLength), diffLength); |
| return; |
| } |
| |
| // tough case, the new field is bigger than the old field ... |
| // first attempt, see how much space is in row private reserved space |
| |
| int extraLength = newLength - oldLength; |
| |
| // extraLength is always greater than 0. |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(extraLength > 0); |
| |
| int recordReservedSpace = getReservedCount(slot); |
| int reservedDelta = 0; |
| |
| int spaceRequiredFromFreeSpace = extraLength - recordReservedSpace; |
| |
| if (SanityManager.DEBUG) { |
| if (spaceRequiredFromFreeSpace > freeSpace) |
| SanityManager.THROWASSERT( |
| "spaceRequiredFromFreeSpace = " + |
| spaceRequiredFromFreeSpace + |
| ";freeSpace = " + freeSpace + |
| ";newLength = " + newLength + |
| ";oldLength = " + oldLength + |
| ";\npage= " + this); |
| } |
| |
| if (spaceRequiredFromFreeSpace > 0) { |
| // The update requires all the reserved space + some from free space |
| |
| int nextRecordOffset = getRecordOffset(slot) + getTotalSpace(slot); |
| |
| // see if this is the last record on the page, if so a simple |
| // shift of the remaining fields will sufice... |
| expandPage(nextRecordOffset, spaceRequiredFromFreeSpace); |
| |
| // we used all the reserved space we have, set it to 0 |
| reservedDelta = -(recordReservedSpace); |
| } else { |
| // the update uses some amount of space from the rows reserved space |
| |
| // set reserved Delta to account for amount of reserved space used. |
| reservedDelta = -(extraLength); |
| } |
| |
| // just shift all remaining fields up |
| int remainingLength = shiftRemainingData(slot, offset, oldLength, newLength); |
| |
| if (SanityManager.DEBUG) { |
| if ((extraLength + reservedDelta) < 0) |
| SanityManager.THROWASSERT( |
| "total space the record occupies cannot shrink, extraLength = " |
| + extraLength + " reservedDelta = " + reservedDelta |
| + " spacerequired = " + spaceRequiredFromFreeSpace |
| + " recordReservedSpace = " + recordReservedSpace); |
| } |
| |
| // now reset the length in the slot entry |
| updateRecordPortionLength(slot, extraLength, reservedDelta); |
| } |
| |
| /** |
| storeField |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| */ |
| public void storeField(LogInstant instant, int slot, int fieldNumber, ObjectInput in) |
| throws StandardException, IOException |
| { |
| logAction(instant); |
| |
| int offset = getFieldOffset(slot, fieldNumber); |
| |
| // get the field header information, the input stream came from the log |
| ArrayInputStream lrdi = rawDataIn; |
| lrdi.setPosition(offset); |
| int oldFieldStatus = StoredFieldHeader.readStatus(lrdi); |
| int oldFieldDataLength = StoredFieldHeader.readFieldDataLength(lrdi, oldFieldStatus, slotFieldSize); |
| |
| int newFieldStatus = StoredFieldHeader.readStatus(in); |
| int newFieldDataLength = StoredFieldHeader.readFieldDataLength(in, newFieldStatus, slotFieldSize); |
| newFieldStatus = StoredFieldHeader.setFixed(newFieldStatus, false); |
| |
| int oldFieldLength = StoredFieldHeader.size(oldFieldStatus, oldFieldDataLength, slotFieldSize) + oldFieldDataLength; |
| int newFieldLength = StoredFieldHeader.size(newFieldStatus, newFieldDataLength, slotFieldSize) + newFieldDataLength; |
| |
| createSpaceForUpdate(slot, offset, oldFieldLength, newFieldLength); |
| |
| rawDataOut.setPosition(offset); |
| offset += StoredFieldHeader.write(rawDataOut, newFieldStatus, newFieldDataLength, slotFieldSize); |
| |
| if (newFieldDataLength != 0) |
| in.readFully(pageData, offset, newFieldDataLength); |
| } |
| |
| /** |
| reserveSpaceForSlot |
| This method will reserve at least specified "spaceToReserve" bytes for the record |
| in the slot. |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| */ |
| public void reserveSpaceForSlot(LogInstant instant, int slot, int spaceToReserve) |
| throws StandardException, IOException |
| { |
| logAction(instant); |
| |
| int extraSpace = spaceToReserve - getReservedCount(slot); |
| if (extraSpace <= 0) |
| return; |
| |
| if (freeSpace < extraSpace) |
| throw new NoSpaceOnPage(isOverflowPage()); |
| |
| // need to shift the later records down ... |
| int startingOffset = getRecordOffset(slot); |
| int nextRecordOffset = startingOffset + getTotalSpace(slot); |
| |
| // see if this is the last record on the page, if so a simple |
| // shift of the remaining fields will sufice... |
| expandPage(nextRecordOffset, extraSpace); |
| |
| setSlotEntry(slot, startingOffset, getRecordPortionLength(slot), spaceToReserve); |
| } |
| |
| /** |
| Skip a field header and its data on the given stream. |
| |
| @exception IOException corrupt stream |
| */ |
| public void skipField(ObjectInput in) throws IOException { |
| |
| |
| int fieldStatus = StoredFieldHeader.readStatus(in); |
| int fieldDataLength = StoredFieldHeader.readFieldDataLength(in, fieldStatus, slotFieldSize); |
| |
| if (fieldDataLength != 0) { |
| DataInputUtil.skipFully(in, fieldDataLength); |
| } |
| } |
| |
| public void skipRecord(ObjectInput in) throws IOException |
| { |
| |
| StoredRecordHeader recordHeader = new StoredRecordHeader(); |
| recordHeader.read(in); |
| |
| for (int i = recordHeader.getNumberFields(); i > 0; i--) { |
| skipField(in); |
| } |
| } |
| |
| /** |
| Shift data within a record to account for an update. |
| |
| @param offset Offset where the update starts, need not be on a field boundry. |
| @param oldLength length of the data being replaced |
| @param newLength length of the data replacing the old data |
| |
| @return the length of the data in the record after the replaced data. |
| */ |
| private int shiftRemainingData(int slot, int offset, int oldLength, int newLength) |
| throws IOException |
| { |
| |
| // length of valid data remaining in the record after the portion that |
| // is being replaced. |
| int remainingLength = (getRecordOffset(slot) + getRecordPortionLength(slot)) - |
| (offset + oldLength); |
| |
| if (SanityManager.DEBUG) { |
| |
| if (!(((remainingLength >= 0) && |
| (getRecordPortionLength(slot) >= oldLength)))) |
| { |
| SanityManager.THROWASSERT( |
| "oldLength = " + oldLength + " newLength = " + newLength + |
| "remainingLength = " + remainingLength + |
| " offset = " + offset + |
| " getRecordOffset(" + slot + ") = " + getRecordOffset(slot)+ |
| " getRecordPortionLength(" + slot + ") = " + |
| getRecordPortionLength(slot)); |
| } |
| } |
| |
| if (remainingLength != 0) { |
| System.arraycopy(pageData, offset + oldLength, |
| pageData, offset + newLength, remainingLength); |
| } |
| |
| return remainingLength; |
| |
| } |
| |
| /** |
| Set the deleted status |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| @see BasePage#setDeleteStatus |
| */ |
| public void setDeleteStatus(LogInstant instant, int slot, boolean delete) |
| throws StandardException, IOException |
| { |
| |
| logAction(instant); |
| |
| deletedRowCount += super.setDeleteStatus(slot, delete); |
| headerOutOfDate = true; |
| |
| int offset = getRecordOffset(slot); |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| rawDataOut.setPosition(offset); |
| recordHeader.write(logicalDataOut); |
| } |
| |
| /** |
| get record count without checking for latch |
| */ |
| protected int internalDeletedRecordCount() |
| { |
| return deletedRowCount; |
| } |
| |
| /** |
| purgeRecord from page. Move following slots up by one. |
| |
| @exception StandardException Standard Derby error policy |
| @exception IOException RESOLVE |
| */ |
| public void purgeRecord(LogInstant instant, int slot, int recordId) |
| throws StandardException, IOException |
| { |
| |
| logAction(instant); |
| |
| // if record is marked deleted, reduce deletedRowCount |
| if (getHeaderAtSlot(slot).isDeleted()) |
| deletedRowCount--; |
| |
| int startByte = getRecordOffset(slot); |
| int endByte = startByte + getTotalSpace(slot) - 1; |
| |
| compressPage(startByte, endByte); |
| |
| // fix up the on-page slot table |
| removeSlotEntry(slot); |
| |
| // fix up the in-memory version |
| removeAndShiftDown(slot); |
| } |
| |
| /* |
| ** |
| */ |
| |
| /** |
| Get the offset of the field header of the given field for |
| the record in the given slot. |
| |
| Field number is the absolute number for the complete record, not just this portion. |
| E.g. if this is a record portion that starts at field 3 and has 6 fields |
| then the second field on this *page* has field number 4. |
| */ |
| private int getFieldOffset(int slot, int fieldNumber) throws IOException |
| { |
| // RESOLVE - overflow, needs to be changed |
| int offset = getRecordOffset(slot); |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| // get the number of fields |
| int startField = recordHeader.getFirstField(); |
| |
| if (SanityManager.DEBUG) { |
| int numberFields = recordHeader.getNumberFields(); |
| |
| if ((fieldNumber < startField) || (fieldNumber >= (startField + numberFields))) |
| SanityManager.THROWASSERT( |
| "fieldNumber: " + fieldNumber + |
| " start field: " + startField + |
| " number of fields " + numberFields); |
| } |
| |
| ArrayInputStream lrdi = rawDataIn; |
| |
| // skip the record header |
| lrdi.setPosition(offset + recordHeader.size()); |
| |
| // skip any earlier fields ... |
| for (int i = startField; i < fieldNumber; i++) { |
| skipField(lrdi); |
| } |
| |
| return rawDataIn.getPosition(); |
| } |
| |
| |
| /* |
| * Time stamp support - this page supports time stamp |
| */ |
| |
| /** |
| Get a time stamp for this page |
| @return page time stamp |
| */ |
| public PageTimeStamp currentTimeStamp() |
| { |
| // saving the whole key would be an overkill |
| return new PageVersion(getPageNumber(), getPageVersion()); |
| } |
| |
| /** |
| Set given pageVersion to be the as what is on this page |
| |
| @exception StandardException given time stamp is null or is not a time |
| stamp implementation this page knows how to deal with |
| */ |
| public void setTimeStamp(PageTimeStamp ts) throws StandardException |
| { |
| if (ts == null) |
| { |
| throw StandardException.newException(SQLState.DATA_TIME_STAMP_NULL); |
| } |
| |
| if (!(ts instanceof PageVersion)) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_TIME_STAMP_ILLEGAL, ts); |
| } |
| |
| PageVersion pv = (PageVersion)ts; |
| |
| pv.setPageNumber(getPageNumber()); |
| pv.setPageVersion(getPageVersion()); |
| } |
| |
| /** |
| compare given PageVersion with pageVersion on page |
| |
| @param ts the page version gotton from this page via a currentTimeStamp |
| or setTimeStamp call earlier |
| |
| @return true if the same |
| @exception StandardException given time stamp not gotton from this page |
| */ |
| public boolean equalTimeStamp(PageTimeStamp ts) throws StandardException |
| { |
| if (ts == null) |
| return false; |
| |
| if (!(ts instanceof PageVersion)) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_TIME_STAMP_ILLEGAL, ts); |
| } |
| |
| PageVersion pv = (PageVersion)ts; |
| |
| if (pv.getPageNumber() != getPageNumber()) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_TIME_STAMP_ILLEGAL, ts); |
| } |
| |
| return (pv.getPageVersion() == getPageVersion()); |
| } |
| |
| /** debugging, print this page */ |
| public String toString() |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("DeadlockTrace") || SanityManager.DEBUG_ON("userLockStackTrace")) |
| return "page = " + getIdentity(); |
| |
| String str = "---------------------------------------------------\n"; |
| str += pageHeaderToString(); |
| // str += slotTableToString(); // print in memory slot table |
| |
| // now print each row |
| for (int s = 0; s < slotsInUse; s++) |
| str += recordToString(s); |
| |
| //if (SanityManager.DEBUG_ON("dumpPageImage")) |
| { |
| str += "---------------------------------------------------\n"; |
| str += pagedataToHexDump(pageData); |
| str += "---------------------------------------------------\n"; |
| } |
| return str; |
| } |
| else |
| return null; |
| } |
| |
| public String toUncheckedString() |
| { |
| if (SanityManager.DEBUG) |
| { |
| String str = "---------------------------------------------------\n"; |
| str += pageHeaderToString(); |
| |
| //if (SanityManager.DEBUG_ON("dumpPageImage")) |
| { |
| str += "---------------------------------------------------\n"; |
| str += pagedataToHexDump(pageData); |
| str += "---------------------------------------------------\n"; |
| } |
| return str; |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Provide a hex dump of the data in the in memory version of the page. |
| * <p> |
| * The output looks like: |
| * |
| * 00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000 MZ.............. |
| * 00000010: b800 0000 0000 0000 4000 0000 0000 0000 ........@....... |
| * 00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ |
| * 00000030: 0000 0000 0000 0000 0000 0000 8000 0000 ................ |
| * 00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th |
| * 00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f is program canno |
| * 00000060: 7420 6265 2072 756e 2069 6e20 444f 5320 t be run in DOS |
| * 00000070: 6d6f 6465 2e0d 0a24 0000 0000 0000 0050 mode...$.......P |
| * 00000080: 4500 004c 0109 008b abfd 3000 0000 0000 E..L......0..... |
| * 00000090: 0000 00e0 000e 210b 0102 3700 3405 0000 ......!...7.4... |
| * 000000a0: 8401 0000 6400 0000 6004 0000 1000 0000 ....d...`....... |
| * 000000b0: 5005 0000 0008 6000 1000 0000 0200 0001 P.....`......... |
| * 000000c0: 0000 0000 0000 0004 0000 0000 0000 0000 ................ |
| * 000000d0: 9007 0000 0400 0009 a207 0002 0000 0000 ................ |
| * 000000e0: 0010 0000 1000 0000 0010 0000 1000 0000 ................ |
| * 000000f0: 0000 0010 0000 0000 6006 00ef 8100 0000 ........`....... |
| * 00000100: 5006 00e6 0c00 0000 0007 00d0 0400 0000 P............... |
| * 00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ |
| * 00000120: 1007 00c8 7100 0000 0000 0000 0000 0000 ....q........... |
| * 00000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................ |
| * |
| * <p> |
| * |
| * @return The string with the hex dump in it. |
| * |
| * @param data array of bytes to dump. |
| **/ |
| private static String pagedataToHexDump(byte[] data) |
| { |
| return org.apache.derby.iapi.util.StringUtil.hexDump(data); |
| } |
| |
| private String pageHeaderToString() |
| { |
| if (SanityManager.DEBUG) |
| { |
| return |
| "page id: " + getIdentity() + |
| " Overflow: " + isOverflowPage + |
| " PageVersion: " + getPageVersion() + |
| " SlotsInUse: " + slotsInUse + |
| " DeletedRowCount: " + deletedRowCount + |
| " PageStatus: " + getPageStatus() + |
| " NextId: " + nextId + |
| " firstFreeByte: " + firstFreeByte + |
| " freeSpace: " + freeSpace + |
| " totalSpace: " + totalSpace + |
| " spareSpace: " + spareSpace + "%" + |
| " minimumRecordSize : " + minimumRecordSize + |
| " PageSize: " + getPageSize() + |
| "\n"; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| String getPageDumpString() |
| { |
| return( |
| MessageService.getTextMessage( |
| MessageId.STORE_PAGE_DUMP, |
| getIdentity(), |
| isOverflowPage, |
| getPageVersion(), |
| slotsInUse, |
| deletedRowCount, |
| getPageStatus(), |
| nextId, |
| firstFreeByte, |
| freeSpace, |
| totalSpace, |
| spareSpace, |
| minimumRecordSize, |
| getPageSize(), |
| pagedataToHexDump(pageData))); |
| } |
| |
| private String recordToString(int slot) |
| { |
| if (SanityManager.DEBUG) |
| { |
| String str = ""; |
| try |
| { |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| int offset = getRecordOffset(slot); |
| int numberFields = recordHeader.getNumberFields(); |
| str = "\nslot " + slot + " offset " + offset + " " + |
| " recordlen " + getTotalSpace(slot) + |
| " (" + getRecordPortionLength(slot) + |
| "," + getReservedCount(slot) + ")"+ |
| recordHeader.toString(); |
| |
| // move offset past record header to begin of first field. |
| offset += recordHeader.size(); |
| rawDataIn.setPosition(offset); |
| |
| for (int i = 0; i < numberFields; i++) |
| { |
| int fieldStatus = StoredFieldHeader.readStatus(rawDataIn); |
| int fieldDataLength = StoredFieldHeader.readFieldDataLength(rawDataIn, fieldStatus, slotFieldSize); |
| if (fieldDataLength < 0) |
| { |
| str += "\n\tField " + i + ": offset=" + offset + " null " + |
| StoredFieldHeader.toDebugString(fieldStatus); |
| } |
| else |
| { |
| str += "\n\tField " + i + ": offset=" + offset + |
| " len=" + fieldDataLength + " " + |
| StoredFieldHeader.toDebugString(fieldStatus); |
| |
| if (StoredFieldHeader.isOverflow(fieldStatus)) |
| { |
| // not likely to be a real pointer, this is most |
| // likely an old column chain where the first field |
| // is set to overflow even though the second field |
| // is the overflow pointer |
| if (i == 0 && fieldDataLength != 3) |
| { |
| // figure out where we should go next |
| offset = rawDataIn.getPosition() + fieldDataLength; |
| long overflowPage = CompressedNumber.readLong((InputStream) rawDataIn); |
| int overflowId = CompressedNumber.readInt((InputStream) rawDataIn); |
| |
| str += "Questionable long column at (" + |
| overflowPage + "," + overflowId + ")"; |
| rawDataIn.setPosition(offset); |
| } |
| else |
| { |
| // print the overflow pointer |
| long overflowPage = CompressedNumber.readLong((InputStream) rawDataIn); |
| int overflowId = CompressedNumber.readInt((InputStream) rawDataIn); |
| str += "long column at (" + overflowPage + "," + overflowId + ")"; |
| } |
| } |
| else |
| { |
| // go to next field |
| offset = rawDataIn.getPosition() + fieldDataLength; |
| rawDataIn.setPosition(offset); |
| } |
| } |
| } |
| str += "\n"; |
| |
| } |
| catch (IOException ioe) |
| { |
| str += "\n ======= ERROR IOException =============\n"; |
| str += ioe.toString(); |
| } |
| catch (StandardException se) |
| { |
| str += "\n ======= ERROR StandardException =============\n"; |
| str += se.toString(); |
| } |
| |
| return str; |
| } |
| else |
| return null; |
| } |
| |
| /* |
| ** Overflow related methods |
| */ |
| |
| /** |
| Get the overflow page for a record that has already overflowed. |
| @exception StandardException Standard Derby error policy |
| */ |
| protected StoredPage getOverflowPage(long pageNumber) throws StandardException |
| { |
| |
| StoredPage overflowPage = (StoredPage) owner.getPage(pageNumber); |
| if (overflowPage == null) { |
| } |
| |
| // RESOLVE-LR |
| //if (!overflowPage.isOverflow()) { |
| // overflowPage.unlatch(); |
| //} |
| |
| return overflowPage; |
| } |
| |
| /** |
| Get an empty overflow page. |
| @exception StandardException Standard Derby error policy |
| */ |
| protected BasePage getNewOverflowPage() throws StandardException |
| { |
| |
| FileContainer myContainer = (FileContainer) containerCache.find(identity.getContainerId()); |
| |
| try { |
| // add an overflow page |
| return (BasePage) myContainer.addPage(owner, true); |
| } finally { |
| containerCache.release(myContainer); |
| } |
| } |
| |
| /** |
| Get the overflow slot for a record that has already overflowed. |
| @exception StandardException Standard Derby error policy |
| */ |
| protected static int getOverflowSlot(BasePage overflowPage, StoredRecordHeader recordHeader) |
| throws StandardException |
| { |
| |
| int slot = overflowPage.findRecordById( |
| recordHeader.getOverflowId(), Page.FIRST_SLOT_NUMBER); |
| |
| if (slot < 0) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_SLOT_NOT_ON_PAGE); |
| } |
| |
| return slot; |
| } |
| |
| /** |
| Get a overflow page that potentially can handle a new overflowed record. |
| @exception StandardException Standard Derby error policy |
| */ |
| public BasePage getOverflowPageForInsert( |
| int currentSlot, |
| Object[] row, |
| FormatableBitSet validColumns) |
| throws StandardException |
| { |
| return getOverflowPageForInsert(currentSlot, row, validColumns, 0); |
| } |
| |
| /** |
| @exception StandardException Standard Derby error policy |
| */ |
| public BasePage getOverflowPageForInsert( |
| int currentSlot, |
| Object[] row, |
| FormatableBitSet validColumns, |
| int startColumn) |
| throws StandardException |
| { |
| // System.out.println("Top of getOverflowPageForInsert"); |
| |
| // look at all the overflow pages that are in use on this page, up |
| // to a maximum of 5. |
| long[] pageList = new long[5]; |
| int pageCount = 0; |
| |
| long currentOverflowPageNumber = 0; |
| |
| slotScan: |
| for (int slot = 0; (slot < slotsInUse) && (pageCount < pageList.length); slot++) { |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| if (!recordHeader.hasOverflow()) |
| continue; |
| |
| long overflowPageNumber = recordHeader.getOverflowPage(); |
| |
| if (slot == currentSlot) { |
| currentOverflowPageNumber = overflowPageNumber; |
| continue; |
| } |
| |
| for (int i = 0; i < pageCount; i++) { |
| if (pageList[i] == overflowPageNumber) |
| continue slotScan; |
| } |
| |
| pageList[pageCount++] = overflowPageNumber; |
| } |
| |
| |
| for (int i = 0; i < pageCount; i++) { |
| |
| long pageNumber = pageList[i]; |
| |
| // don't look at the current overflow page |
| // used by this slot, because it the record is already |
| // overflowed then we reached here because the overflow |
| // page is full. |
| if (pageNumber == currentOverflowPageNumber) |
| continue; |
| StoredPage overflowPage = null; |
| int spaceNeeded = 0; |
| try { |
| overflowPage = getOverflowPage(pageNumber); |
| if ( overflowPage.spaceForInsert(row, validColumns, |
| spaceNeeded, startColumn, 100)) |
| { |
| // System.out.println("returning used page: " + pageNumber); |
| return overflowPage; |
| } |
| |
| spaceNeeded = ((StoredPage) overflowPage).getCurrentFreeSpace(); |
| overflowPage.unlatch(); |
| overflowPage = null; |
| |
| } catch (StandardException se) { |
| if (overflowPage != null) { |
| overflowPage.unlatch(); |
| overflowPage = null; |
| } |
| |
| } |
| } |
| |
| // if we get here then we have to allocate a new overflow page |
| // System.out.println("returning new page: "); |
| return getNewOverflowPage(); |
| } |
| |
| /** |
| Update an already overflowed record. |
| |
| @param slot Slot of the original record on its original page |
| @param row new version of the data |
| |
| @exception StandardException Standard Derby error policy |
| */ |
| protected void updateOverflowed( |
| RawTransaction t, |
| int slot, |
| Object[] row, |
| FormatableBitSet validColumns, |
| StoredRecordHeader recordHeader) |
| throws StandardException |
| { |
| |
| BasePage overflowPage = getOverflowPage(recordHeader.getOverflowPage()); |
| |
| try { |
| |
| int overflowSlot = getOverflowSlot(overflowPage, recordHeader); |
| |
| overflowPage.doUpdateAtSlot(t, overflowSlot, recordHeader.getOverflowId(), row, validColumns); |
| overflowPage.unlatch(); |
| overflowPage = null; |
| |
| return; |
| |
| } finally { |
| if (overflowPage != null) { |
| overflowPage.unlatch(); |
| overflowPage = null; |
| } |
| } |
| } |
| |
| |
| /** |
| Update a record handle to point to an overflowed record portion. |
| Note that the record handle need not be the current page. |
| @exception StandardException Standard Derby error policy |
| */ |
| public void updateOverflowDetails(RecordHandle handle, RecordHandle overflowHandle) |
| throws StandardException |
| { |
| long handlePageNumber = handle.getPageNumber(); |
| if (handlePageNumber == getPageNumber()) { |
| updateOverflowDetails(this, handle, overflowHandle); |
| return; |
| } |
| |
| StoredPage handlePage = (StoredPage) owner.getPage(handlePageNumber); |
| |
| updateOverflowDetails(handlePage, handle, overflowHandle); |
| handlePage.unlatch(); |
| } |
| |
| private void updateOverflowDetails(StoredPage handlePage, RecordHandle handle, RecordHandle overflowHandle) |
| throws StandardException { |
| // update the temp record header, this will be used in the log row .. |
| handlePage.getOverFlowRecordHeader().setOverflowDetails(overflowHandle); |
| |
| // Use the slot interface as we don't need a lock since |
| // the initial insert/update holds the lock on the first |
| // portion of the record. |
| int slot = handlePage.getSlotNumber(handle); |
| |
| // use doUpdateAtSlot as it avoids unnecessary work in updateAtSlot the |
| // null indicates to this page that the record should become an |
| // overflow record |
| handlePage.doUpdateAtSlot( |
| owner.getTransaction(), slot, handle.getId(), |
| (Object[]) null, (FormatableBitSet) null); |
| } |
| |
| /** |
| @exception StandardException Standard Derby error policy |
| */ |
| public void updateFieldOverflowDetails(RecordHandle handle, RecordHandle overflowHandle) |
| throws StandardException |
| { |
| // add an overflow field at the end of the previous record |
| // uses sparse rows |
| Object[] row = new Object[2]; |
| row[1] = overflowHandle; |
| |
| // we are expanding the record to have 2 fields, the second field is the overflow pointer. |
| FormatableBitSet validColumns = new FormatableBitSet(2); |
| validColumns.set(1); |
| |
| // Use the slot interface as we don't need a lock since |
| // the initial insert/update holds the lock on the first |
| // portion of the record. |
| int slot = getSlotNumber(handle); |
| |
| // use doUpdateAtSlot as it avoids unnecessary work in updateAtSlot |
| doUpdateAtSlot(owner.getTransaction(), slot, handle.getId(), row, validColumns); |
| } |
| |
| /** |
| @exception StandardException Standard Derby error policy |
| */ |
| public int appendOverflowFieldHeader(DynamicByteArrayOutputStream logBuffer, RecordHandle overflowHandle) |
| throws StandardException, IOException |
| { |
| int fieldStatus = StoredFieldHeader.setInitial(); |
| fieldStatus = StoredFieldHeader.setOverflow(fieldStatus, true); |
| |
| long overflowPage = overflowHandle.getPageNumber(); |
| int overflowId = overflowHandle.getId(); |
| int fieldDataLength = CompressedNumber.sizeLong(overflowPage) |
| + CompressedNumber.sizeInt(overflowId); |
| |
| // write the field header to the log buffer |
| int lenWritten = StoredFieldHeader.write(logBuffer, fieldStatus, fieldDataLength, slotFieldSize); |
| |
| // write the overflow details to the log buffer |
| lenWritten += CompressedNumber.writeLong(logBuffer, overflowPage); |
| lenWritten += CompressedNumber.writeInt(logBuffer, overflowId); |
| |
| // this length is the same on page as in the log |
| return (lenWritten); |
| } |
| |
| protected int getSlotsInUse() |
| { |
| return(slotsInUse); |
| } |
| |
| |
| /** |
| return the max datalength allowed with the space available |
| */ |
| private int getMaxDataLength(int spaceAvailable, int overflowThreshold) { |
| |
| if (SanityManager.DEBUG) { |
| if (overflowThreshold == 0) |
| SanityManager.THROWASSERT("overflowThreshold cannot be 0"); |
| } |
| |
| // we need to take into considering of the overflowThreshold |
| // the overflowThreshold limits the max data length, |
| // whatever space we have left, we will not allow max data length |
| // to exceed the overflow threshold. |
| int maxThresholdSpace = totalSpace * overflowThreshold / 100; |
| int maxAvailable = 0; |
| |
| if (spaceAvailable < (64 - 2)) |
| maxAvailable = spaceAvailable - 2; |
| else if (spaceAvailable < (16383 - 3)) |
| maxAvailable = spaceAvailable - 3; |
| else |
| maxAvailable = spaceAvailable - 5; |
| |
| return (maxAvailable > maxThresholdSpace ? maxThresholdSpace : maxAvailable); |
| |
| } |
| |
| /** |
| return whether the field has exceeded the max threshold for this page |
| it compares the fieldSize with the largest possible field for this page |
| */ |
| private boolean isLong(int fieldSize, int overflowThreshold) { |
| |
| if (SanityManager.DEBUG) { |
| if (overflowThreshold == 0) |
| SanityManager.THROWASSERT("overflowThreshold cannot be 0"); |
| } |
| |
| // if a field size is over the threshold, then it becomes a long column |
| int maxThresholdSize = maxFieldSize * overflowThreshold / 100; |
| return (fieldSize > maxThresholdSize); |
| } |
| |
| /** |
| Perform an update. |
| |
| @exception StandardException Standard Derby policy |
| */ |
| public void doUpdateAtSlot( |
| RawTransaction t, |
| int slot, |
| int id, |
| Object[] row, |
| FormatableBitSet validColumns) |
| throws StandardException |
| { |
| // If this is a head page, the recordHandle is the head row handle. |
| // If this is not a head page, we are calling updateAtSlot inside some |
| // convoluted loop that updates an overflow chain. There is nothing we |
| // can doing about it anyway. |
| RecordHandle headRowHandle = |
| isOverflowPage() ? null : getRecordHandleAtSlot(slot); |
| |
| // RESOLVE: djd/yyz what does a null row means? (sku) |
| if (row == null) |
| { |
| owner.getActionSet().actionUpdate( |
| t, this, slot, id, row, validColumns, -1, |
| (DynamicByteArrayOutputStream) null, -1, headRowHandle); |
| |
| return; |
| } |
| |
| // startColumn is the first column to be updated. |
| int startColumn = RowUtil.nextColumn(row, validColumns, 0); |
| if (startColumn == -1) |
| return; |
| |
| if (SanityManager.DEBUG) |
| { |
| // make sure that if N bits are set in the validColumns that |
| // exactly N columns are passed in via the row array. |
| if (!isOverflowPage() && validColumns != null) |
| { |
| if (RowUtil.getNumberOfColumns(-1, validColumns) > row.length) |
| SanityManager.THROWASSERT("updating slot " + slot + |
| " on page " + getIdentity() + " " + |
| RowUtil.getNumberOfColumns(-1, validColumns) + |
| " bits are set in validColumns but only " + |
| row.length + " columns in row[]"); |
| } |
| } |
| |
| |
| // Keep track of row shrinkage in the head row piece. If any row piece |
| // shrinks, file a post commit work to clear all reserved space for the |
| // entire row chain. |
| boolean rowHasReservedSpace = false; |
| |
| StoredPage curPage = this; |
| for (;;) |
| { |
| StoredRecordHeader rh = curPage.getHeaderAtSlot(slot); |
| |
| int startField = rh.getFirstField(); |
| int endFieldExclusive = startField + rh.getNumberFields(); |
| |
| // curPage contains column[startField] to column[endFieldExclusive-1] |
| |
| // Need to cope with an update that is increasing the number of |
| // columns. If this occurs we want to make sure that we perform a |
| // single update to the last portion of a record, and not an update |
| // of the current columns and then an update to append a column. |
| |
| long nextPage = -1; |
| int realStartColumn = -1; |
| int realSpaceOnPage = -1; |
| |
| if (!rh.hasOverflow() || |
| ((startColumn >= startField) && |
| (startColumn < endFieldExclusive))) |
| { |
| boolean hitLongColumn; |
| int nextColumn = -1; |
| Object[] savedFields = null; |
| DynamicByteArrayOutputStream logBuffer = null; |
| |
| do |
| { |
| try |
| { |
| // Update this portion of the record. |
| // Pass in headRowHandle in case we are to update any |
| // long column and they need to be cleaned up by post |
| // commit processing. We don't want to purge the |
| // columns right now because in order to reclaim the |
| // page, we need to remove them. But it would be bad |
| // to remove them now because the transaction may not |
| // commit for a long time. We can do both purging of |
| // the long column and page removal together in the |
| // post commit. |
| nextColumn = |
| owner.getActionSet().actionUpdate( |
| t, curPage, slot, id, row, validColumns, |
| realStartColumn, logBuffer, |
| realSpaceOnPage, headRowHandle); |
| |
| hitLongColumn = false; |
| |
| } |
| catch (LongColumnException lce) |
| { |
| |
| if (lce.getRealSpaceOnPage() == -1) |
| { |
| // an update that has caused the row to increase |
| // in size *and* push some fields off the page |
| // that need to be inserted in an overflow page |
| |
| // no need to make a copy as we are going to use |
| // this buffer right away |
| logBuffer = lce.getLogBuffer(); |
| |
| savedFields = |
| (Object[]) lce.getColumn(); |
| |
| realStartColumn = lce.getNextColumn(); |
| realSpaceOnPage = -1; |
| |
| hitLongColumn = true; |
| |
| continue; |
| } |
| |
| |
| // we caught a real long column exception |
| // three things should happen here: |
| // 1. insert the long column into overflow pages. |
| // 2. append the overflow field header in the main chain. |
| // 3. continue the update in the main data chain. |
| logBuffer = |
| new DynamicByteArrayOutputStream(lce.getLogBuffer()); |
| |
| // step 1: insert the long column ... if this update |
| // operation rolls back, purge the after image column |
| // chain and reclaim the overflow page because the |
| // whole chain will be orphaned anyway. |
| RecordHandle longColumnHandle = |
| insertLongColumn( |
| curPage, lce, Page.INSERT_UNDO_WITH_PURGE); |
| |
| // step 2: append overflow field header to log buffer |
| int overflowFieldLen = 0; |
| try |
| { |
| overflowFieldLen += |
| appendOverflowFieldHeader( |
| logBuffer, longColumnHandle); |
| |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| |
| // step 3: continue the insert in the main data chain |
| // need to pass the log buffer, and start column to the |
| // next insert. |
| realStartColumn = lce.getNextColumn() + 1; |
| realSpaceOnPage = lce.getRealSpaceOnPage() - overflowFieldLen; |
| hitLongColumn = true; |
| |
| } |
| catch (NoSpaceOnPage nsop) |
| { |
| // DERBY-4923 |
| // |
| // The actionUpdate() call should not generate a |
| // NoSpaceOnPage error. |
| |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_NO_SPACE_ON_PAGE, |
| nsop, |
| ((PageKey) curPage.getIdentity()).toString(), |
| getPageDumpString(), |
| slot, |
| id, |
| validColumns.toString(), |
| realStartColumn, |
| 0, |
| headRowHandle); |
| } |
| |
| } while (hitLongColumn); |
| |
| |
| // See if we completed all the columns that are on this page. |
| int validColumnsSize = |
| (validColumns == null) ? 0 : validColumns.getLength(); |
| |
| if (nextColumn != -1) |
| { |
| |
| if (SanityManager.DEBUG) |
| { |
| // note nextColumn might be less than the the first |
| // column we started updating. This is because the |
| // update might force the record header to grow and |
| // push fields before the one we are updating off the |
| // page and into this insert. |
| |
| if ((nextColumn < startField) || |
| (rh.hasOverflow() && (nextColumn >= endFieldExclusive))) |
| { |
| SanityManager.THROWASSERT( |
| "nextColumn out of range = " + nextColumn + |
| " expected between " + |
| startField + " and " + endFieldExclusive); |
| } |
| } |
| |
| // Need to insert rows from nextColumn to endFieldExclusive |
| // onto a new overflow page. |
| // If the column is not being updated we |
| // pick it up from the current page. If it is being updated |
| // we take it from the new value. |
| int possibleLastFieldExclusive = endFieldExclusive; |
| |
| if (!rh.hasOverflow()) |
| { |
| // we might be adding a field here |
| if (validColumns == null) |
| { |
| if (row.length > possibleLastFieldExclusive) |
| possibleLastFieldExclusive = row.length; |
| } |
| else |
| { |
| if (validColumnsSize > possibleLastFieldExclusive) |
| possibleLastFieldExclusive = validColumnsSize; |
| } |
| } |
| |
| |
| // use a sparse row |
| Object[] newRow = |
| new Object[possibleLastFieldExclusive]; |
| |
| FormatableBitSet newColumnList = |
| new FormatableBitSet(possibleLastFieldExclusive); |
| |
| ByteArrayOutputStream fieldStream = null; |
| |
| for (int i = nextColumn; i < possibleLastFieldExclusive; i++) |
| { |
| if ((validColumns == null) || |
| (validColumnsSize > i && validColumns.isSet(i))) |
| { |
| newColumnList.set(i); |
| // use the new value |
| newRow[i] = RowUtil.getColumn(row, validColumns, i); |
| |
| } |
| else if (i < endFieldExclusive) |
| { |
| newColumnList.set(i); |
| |
| // use the old value |
| newRow[i] = savedFields[i - nextColumn]; |
| } |
| } |
| |
| RecordHandle handle = curPage.getRecordHandleAtSlot(slot); |
| |
| // If the portion we just updated is the last portion then |
| // there cannot be any updates to do. |
| if (rh.hasOverflow()) |
| { |
| // We have to carry across the overflow information |
| // from the current record, if any. |
| nextPage = rh.getOverflowPage(); |
| id = rh.getOverflowId(); |
| |
| // find the next starting column before unlatching page |
| startColumn = |
| RowUtil.nextColumn( |
| row, validColumns, endFieldExclusive); |
| } |
| else |
| { |
| startColumn = -1; |
| nextPage = 0; |
| } |
| |
| |
| // After the update is done, see if this row piece has |
| // shrunk in curPage if no other row pieces have shrunk so |
| // far. In head page, need to respect minimumRecordSize. |
| // In overflow page entire row needs to respect |
| // StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE. |
| // Don't bother with temp container. |
| if (!rowHasReservedSpace && headRowHandle != null && |
| curPage != null && !owner.isTemporaryContainer()) |
| { |
| rowHasReservedSpace = |
| curPage.checkRowReservedSpace(slot); |
| } |
| |
| |
| // insert the record portion on a new overflow page at slot |
| // 0 this will automatically handle any overflows in |
| // this new portion |
| |
| // BasePage op = getNewOverflowPage(); |
| |
| BasePage op = |
| curPage.getOverflowPageForInsert( |
| slot, |
| newRow, |
| newColumnList, |
| nextColumn); |
| |
| // We have all the information from this page so unlatch it |
| if (curPage != this) |
| { |
| curPage.unlatch(); |
| curPage = null; |
| } |
| |
| byte mode = Page.INSERT_OVERFLOW; |
| if (nextPage != 0) |
| mode |= Page.INSERT_FOR_SPLIT; |
| |
| RecordHandle nextPortionHandle = |
| nextPage == 0 ? null : |
| owner.makeRecordHandle(nextPage, id); |
| |
| // RESOLVED (sku): even though we would like to roll back |
| // these inserts with PURGE rather than with delete, |
| // we have to delete because if we purge the last row |
| // from an overflow page, the purge will queue a post |
| // commit to remove the page. |
| // While this is OK with long columns, we cannot do this |
| // for long rows because long row overflow pages can be |
| // shared by more than one long rows, and thus it is unsafe |
| // to remove the page without first latching the head page. |
| // However, the insert log record do not have the head |
| // row's page number so the rollback cannot put that |
| // information into the post commit work. |
| |
| RecordHandle portionHandle; |
| |
| try |
| { |
| portionHandle = |
| op.insertAllowOverflow( |
| 0, newRow, newColumnList, nextColumn, mode, 100, |
| nextPortionHandle); |
| |
| } |
| catch (NoSpaceOnPage nsop) |
| { |
| // DERBY-6319, intermittently application is getting |
| // an unexpected NoSpaceOnPage error from the |
| // insertAllowOverflow() call. The code expects that |
| // the latched page returned by the above |
| // getOverflowPageForInsert() call should always have |
| // at least enough space to insert with allowing |
| // overflow. |
| // Not sure what is causing this and have not been able |
| // to repro outside of the application, so catching the |
| // error and raising a new error with more information |
| // to be printed to the log. |
| // |
| |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_NO_SPACE_ON_PAGE, |
| nsop, |
| ((PageKey) op.getIdentity()).toString(), |
| getPageDumpString(), |
| slot, |
| id, |
| newColumnList.toString(), |
| nextColumn, |
| mode, |
| nextPortionHandle); |
| } |
| |
| // Update the previous record header to point to new portion |
| if (curPage == this) |
| updateOverflowDetails(this, handle, portionHandle); |
| else |
| updateOverflowDetails(handle, portionHandle); |
| |
| op.unlatch(); |
| |
| } |
| else |
| { |
| |
| // See earlier comments on checking row reserved space. |
| if (!rowHasReservedSpace && |
| headRowHandle != null && |
| curPage != null && |
| !owner.isTemporaryContainer()) |
| { |
| rowHasReservedSpace = |
| curPage.checkRowReservedSpace(slot); |
| } |
| |
| |
| // find the next starting column before we unlatch the page |
| startColumn = |
| rh.hasOverflow() ? |
| RowUtil.nextColumn( |
| row, validColumns, endFieldExclusive) : -1; |
| } |
| |
| // have we completed this update? |
| if (startColumn == -1) { |
| |
| if ((curPage != this) && (curPage != null)) |
| curPage.unlatch(); |
| break; // break out of the for loop |
| } |
| } |
| |
| if (nextPage == -1) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| curPage != null, |
| "Current page is null be no overflow information has been obtained"); |
| } |
| |
| // Get the next page info while we still have the page |
| // latched. |
| nextPage = rh.getOverflowPage(); |
| id = rh.getOverflowId(); |
| } |
| |
| if ((curPage != this) && (curPage != null)) |
| curPage.unlatch(); |
| |
| // get the next portion page and find the correct slot |
| curPage = (StoredPage) owner.getPage(nextPage); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| curPage.isOverflowPage(), |
| "following row chain gets a non-overflow page"); |
| } |
| |
| slot = curPage.findRecordById(id, FIRST_SLOT_NUMBER); |
| } |
| |
| // Back to the head page. Get rid of all reserved space in the entire |
| // row post commit. |
| if (rowHasReservedSpace) |
| { |
| RawTransaction rxact = (RawTransaction)owner.getTransaction(); |
| |
| ReclaimSpace work = |
| new ReclaimSpace(ReclaimSpace.ROW_RESERVE, |
| headRowHandle, |
| rxact.getDataFactory(), true); |
| rxact.addPostCommitWork(work); |
| } |
| } |
| |
| /** |
| */ |
| |
| /** |
| * See if reserved space should be reclaimed for the input row. |
| * <p> |
| * See if the row on this page has reserved space that should be shrunk |
| * once the update commits. Will only indicate space should be reclaimed |
| * if at least RawTransaction.MINIMUM_RECORD_SIZE_DEFAULT bytes can be |
| * reclaimed. |
| * <p> |
| * |
| * @return true if space should be reclaimed from this row post commit. |
| **/ |
| private boolean checkRowReservedSpace(int slot) throws StandardException |
| { |
| boolean rowHasReservedSpace = false; |
| |
| try |
| { |
| int shrinkage = getReservedCount(slot); |
| |
| // Only reclaim reserved space if it is "reasonably" sized, i.e., |
| // we can reclaim at least MINIMUM_RECORD_SIZE_DEFAULT. Note |
| // any number could be used for "reasonable", not sure why |
| // MINIMUM_RECORD_SIZE_DEFAULT was chosen. |
| int reclaimThreshold = RawStoreFactory.MINIMUM_RECORD_SIZE_DEFAULT; |
| |
| if (shrinkage > reclaimThreshold) |
| { |
| // reserved space for row exceeds the threshold. |
| |
| int totalSpace = getRecordPortionLength(slot) + shrinkage; |
| |
| if (isOverflowPage()) |
| { |
| // For overflow pages the total row size, including |
| // reserved space must be at least |
| // StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE |
| |
| if (totalSpace > |
| (StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE + |
| reclaimThreshold)) |
| { |
| // row can reclaim at least the threshold space |
| rowHasReservedSpace = true; |
| } |
| } |
| else |
| { |
| // this is a head page. The total space of the row |
| // including reserved space must total at least |
| // minimumRecordSize. |
| |
| if (totalSpace > (minimumRecordSize + reclaimThreshold)) |
| { |
| // row can reclaim at least the threshold space |
| rowHasReservedSpace = true; |
| } |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| |
| return rowHasReservedSpace; |
| } |
| |
| /** |
| @see BasePage#compactRecord |
| @exception StandardException Standard Derby error policy |
| */ |
| protected void compactRecord(RawTransaction t, int slot, int id) |
| throws StandardException |
| { |
| if (!isOverflowPage()) |
| { |
| // If this is a head row piece, first take care of the entire |
| // overflow row chain. Don't need to worry about long column |
| // because they are not in place updatable. |
| |
| StoredRecordHeader recordHeader = getHeaderAtSlot(slot); |
| |
| while (recordHeader.hasOverflow()) |
| { |
| // loop calling compact on each piece of the overflow chain. |
| |
| StoredPage nextPageInRowChain = |
| getOverflowPage(recordHeader.getOverflowPage()); |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(nextPageInRowChain != null); |
| |
| try |
| { |
| int nextId = recordHeader.getOverflowId(); |
| int nextSlot = |
| getOverflowSlot(nextPageInRowChain, recordHeader); |
| |
| nextPageInRowChain.compactRecord(t, nextSlot, nextId); |
| |
| // Follow the next long row pointer. |
| recordHeader = nextPageInRowChain.getHeaderAtSlot(nextSlot); |
| } |
| finally |
| { |
| nextPageInRowChain.unlatch(); |
| } |
| } |
| } |
| |
| // Lastly, see if this row has anything sizable that can be freed. |
| // Try to only reclaim space larger than MINIMUM_RECORD_SIZE_DEFAULT |
| // because otherwise it is probably not worth the effort. |
| int reclaimThreshold = RawStoreFactory.MINIMUM_RECORD_SIZE_DEFAULT; |
| |
| try |
| { |
| int reserve = getReservedCount(slot); |
| |
| if (reserve > reclaimThreshold) |
| { |
| // unused space exceeds the reclaim threshold. |
| |
| int recordLength = getRecordPortionLength(slot); |
| int correctReservedSpace = reserve; |
| int totalSpace = recordLength + reserve; |
| |
| if (isOverflowPage()) |
| { |
| // On an overflow page the total space of a record must |
| // be at least MAX_OVERFLOW_ONLY_REC_SIZE. |
| |
| if (totalSpace > |
| (StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE + |
| reclaimThreshold)) |
| { |
| // possible to reclaim more than threshold. |
| |
| // calculate what the correct reserved space is |
| if (recordLength >= |
| StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE) |
| { |
| correctReservedSpace = 0; |
| } |
| else |
| { |
| // make sure record takes up max overflow rec size |
| correctReservedSpace = |
| StoredRecordHeader.MAX_OVERFLOW_ONLY_REC_SIZE - |
| recordLength; |
| } |
| } |
| } |
| else |
| { |
| // this is a head page. The total space of the row |
| // including reserved space must total at least |
| // minimumRecordSize. |
| |
| if (totalSpace > (minimumRecordSize + reclaimThreshold)) |
| { |
| // calculate what the correct reserved space is |
| |
| if (recordLength >= minimumRecordSize) |
| { |
| correctReservedSpace = 0; |
| } |
| else |
| { |
| correctReservedSpace = |
| minimumRecordSize - recordLength; |
| } |
| } |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(correctReservedSpace <= reserve, |
| "correct reserve > reserve"); |
| } |
| |
| // A shrinkage has occured. |
| if (correctReservedSpace < reserve) |
| { |
| owner.getActionSet(). |
| actionShrinkReservedSpace( |
| t, this, slot, id, correctReservedSpace, reserve); |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, ioe); |
| } |
| } |
| } |