--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.data.FileContainer\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.store.raw.data;\r
+\r
+import org.apache.derby.iapi.reference.Property;\r
+\r
+import org.apache.derby.iapi.reference.Limits;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.cache.Cacheable;\r
+import org.apache.derby.iapi.services.cache.CacheManager;\r
+import org.apache.derby.iapi.services.context.ContextService;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.services.io.FormatIdOutputStream;\r
+import org.apache.derby.iapi.services.io.StoredFormatIds;\r
+import org.apache.derby.iapi.services.io.TypedFormat;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.store.raw.ContainerHandle;\r
+import org.apache.derby.iapi.store.raw.ContainerKey;\r
+import org.apache.derby.iapi.store.raw.Page;\r
+import org.apache.derby.iapi.store.raw.PageKey;\r
+import org.apache.derby.iapi.store.raw.RecordHandle;\r
+import org.apache.derby.iapi.store.raw.RawStoreFactory;\r
+import org.apache.derby.iapi.store.raw.Transaction;\r
+\r
+import org.apache.derby.iapi.store.raw.log.LogInstant;\r
+import org.apache.derby.iapi.store.raw.xact.RawTransaction;\r
+\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.store.access.AccessFactory;\r
+import org.apache.derby.iapi.store.access.SpaceInfo;\r
+\r
+import org.apache.derby.iapi.services.io.ArrayInputStream;\r
+import org.apache.derby.iapi.services.io.ArrayOutputStream;\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+import org.apache.derby.iapi.util.ByteArray;\r
+\r
+import java.io.IOException;\r
+import java.io.DataInput;\r
+\r
+import java.util.Properties;\r
+import java.util.zip.CRC32;\r
+\r
+import org.apache.derby.io.StorageRandomAccessFile;\r
+\r
+/**\r
+ FileContainer is an abstract base class for containers\r
+ which are based on files.\r
+\r
+ This class extends BaseContainer and implements Cacheable and TypedFormat\r
+*/\r
+\r
+abstract class FileContainer \r
+ extends BaseContainer implements Cacheable, TypedFormat\r
+{\r
+\r
+ /*\r
+ * typed format\r
+ */\r
+\r
+ protected static final int formatIdInteger = \r
+ StoredFormatIds.RAW_STORE_SINGLE_CONTAINER_FILE; \r
+\r
+ // format Id must fit in 4 bytes\r
+\r
+ /**\r
+ Return my format identifier.\r
+ */\r
+ public int getTypeFormatId() \r
+ {\r
+ return StoredFormatIds.RAW_STORE_SINGLE_CONTAINER_FILE;\r
+ }\r
+\r
+ /*\r
+ ** Immutable fields\r
+ */\r
+\r
+ protected final CacheManager pageCache; // my page's cache\r
+ protected final CacheManager containerCache; // cache I am in.\r
+ protected final BaseDataFileFactory dataFactory; // creating factory\r
+\r
+ /*\r
+ ** Fields that are mutable only during identity changes\r
+ */\r
+\r
+ protected int pageSize; // size of my pages\r
+ protected int spareSpace; // % space kept free on page in inserts\r
+ protected int minimumRecordSize; // minimum space a record should \r
+ // occupy on the page.\r
+\r
+ protected short initialPages; // initial number of pages preallocated\r
+ // to the container when created\r
+ \r
+ protected boolean canUpdate; // can I be written to?\r
+\r
+ private int PreAllocThreshold; // how many pages before preallocation \r
+ // kicks in, only stored in memory\r
+ private int PreAllocSize; // how many pages to preallocate at once\r
+ // only stored in memory\r
+ private boolean bulkIncreaseContainerSize;// if true, the next addPage will\r
+ // attempt to preallocate a larger\r
+ // than normal number of pages.\r
+ //\r
+ // preallocation parameters\r
+ private static final int PRE_ALLOC_THRESHOLD = 8;\r
+ private static final int MIN_PRE_ALLOC_SIZE = 1;\r
+ private static final int DEFAULT_PRE_ALLOC_SIZE = 8;\r
+ private static final int MAX_PRE_ALLOC_SIZE = 1000;\r
+\r
+ /* \r
+ ** Mutable fields, only valid when the identity is valid.\r
+ */\r
+\r
+ // RESOLVE: if we run out of bytes in the container, we can change\r
+ // containerVersion from a long to an int because this number is only\r
+ // bumped when the container is dropped (and rolled back), so it is almost\r
+ // impossible for the containverVersion to get beyond a short, let alone\r
+ // and int - someone will have to write an application that attempt to drop\r
+ // the container 2 billion times for that to happen.\r
+ protected long firstAllocPageNumber; // first alloc page number\r
+ protected long firstAllocPageOffset; // first alloc page offset\r
+ protected long containerVersion; // the logged version number\r
+ protected long estimatedRowCount; // value is changed unlogged\r
+ protected LogInstant lastLogInstant; // last time this container \r
+ // object was touched.\r
+ /** \r
+ * The sequence number for reusable recordIds . \r
+ * As long as this number does not change, recordIds will be stable within\r
+ * the container.\r
+ **/\r
+ private long reusableRecordIdSequenceNumber;\r
+\r
+\r
+ /**\r
+ The page that was last inserted into. Use this for getPageForInsert.\r
+ Remember the last allocated non-overflow page, and remember it in\r
+ memory only.\r
+ Use Get/Set method to access this field except when we know it is\r
+ being single thread access.\r
+ */\r
+ private long lastInsertedPage[];\r
+ private int lastInsertedPage_index;\r
+\r
+ /** \r
+ The last unfilled page found. Use this for getPageForInsert.\r
+ Remember the last unfilled page found, and remember it in memory only.\r
+ Use Get/Set method to access this field except when we know it is\r
+ being single thread access.\r
+ */\r
+ private long lastUnfilledPage;\r
+\r
+ /**\r
+ The last allocated page. This global var is access *without*\r
+ synchronization. It is used as a hint for page allocation to find the\r
+ next reusable page.\r
+ */\r
+ private long lastAllocatedPage;\r
+\r
+ /**\r
+ An estimated page count. Use this for getEstimatedPagecount.\r
+ Remember it in memory only.\r
+ */\r
+ private long estimatedPageCount;\r
+\r
+\r
+ // The isDirty flag indicates if the container has been modified. The\r
+ // preDirty flag indicates that the container is about to be modified. The\r
+ // reason for these 2 flags instead of just one is to accomodate\r
+ // checkpoint. After a clean container sends a log record to the log\r
+ // stream but before that conatiner is dirtied by the log operation, a\r
+ // checkpoint could be taken. If so, then the redoLWM will be after the\r
+ // log record but, without preDirty, the cache cleaning will not have\r
+ // waited for the change. So the preDirty bit is to stop the cache\r
+ // cleaning from skipping over this container even though it has not really\r
+ // been modified yet.\r
+ protected boolean preDirty;\r
+ protected boolean isDirty;\r
+\r
+ /*\r
+ allocation information cached by the container object. \r
+\r
+ <P>MT -\r
+ Access to the allocation cache MUST be synchronized on the allocCache\r
+ object. FileContainer manages all MT issue w/r to AllocationCache.\r
+ The AllocationCache object itself is not MT-safe.\r
+ <P>\r
+ The protocol for accessing both the allocation cache and the alloc page\r
+ is: get the alloc cache semaphore, then get the alloc page. Once both\r
+ are held, they can be released in any order.\r
+ <BR>\r
+ It is legal to get one or the other, i.e, it is legal to only get the\r
+ alloc cache semaphore without latching the alloc page, and it is legal\r
+ to get the alloc page latch without the alloc cache semaphore.\r
+ <BR>\r
+ it is illegal to hold alloc page latch and then get the allocation\r
+ cache semaphore\r
+ <PRE>\r
+ Writer to alloc Page (to invalidate alloc cache)\r
+ 1) synchronized(allocCache)\r
+ 2) invalidate cache\r
+ 3) get latch on alloc Page\r
+ 4) release synchonized(allocCache)\r
+\r
+ Reader:\r
+ 1) synchronized(allocCache)\r
+ 2) if valid, read value and release synchronized(allocCache)\r
+ 3) if cache is invalid, get latch on alloc page\r
+ 4) validate cache\r
+ 5) release alloc page latch\r
+ 6) read value\r
+ 7) release synchonized(allocCache)\r
+ </PRE>\r
+ */\r
+ protected AllocationCache allocCache;\r
+\r
+ /*\r
+ * array to store persistently stored fields\r
+ */\r
+ byte[] containerInfo;\r
+\r
+ private CRC32 checksum; // holder for the checksum\r
+\r
+ /*\r
+ ** buffer for encryption/decryption\r
+ */\r
+ private byte[] encryptionBuffer;\r
+\r
+ /*\r
+ * constants\r
+ */\r
+\r
+ /** the container format must fit in this many bytes */\r
+ private static final int CONTAINER_FORMAT_ID_SIZE = 4; \r
+\r
+ /* the checksum size */\r
+ protected static final int CHECKSUM_SIZE = 8;\r
+\r
+ /** \r
+ The size of the persistently stored container info\r
+ ContainerHeader contains the following information:\r
+ 4 bytes int FormatId\r
+ 4 bytes int status\r
+ 4 bytes int pageSize\r
+ 4 bytes int spareSpace\r
+ 4 bytes int minimumRecordSize\r
+ 2 bytes short initialPages\r
+ 2 bytes short spare1\r
+ 8 bytes long first Allocation page number\r
+ 8 bytes long first Allocation page offset\r
+ 8 bytes long container version\r
+ 8 bytes long estimated number of rows\r
+ 8 bytes long reusable recordId sequence number\r
+ 8 bytes long spare3\r
+ 8 bytes long checksum\r
+ container info size is 80 bytes, with 10 bytes of spare space\r
+ */\r
+ protected static final int CONTAINER_INFO_SIZE = \r
+ CONTAINER_FORMAT_ID_SIZE+4+4+4+4+2+2+8+8+8+8+CHECKSUM_SIZE+8+8;\r
+\r
+ /**\r
+ * where the first alloc page is located - \r
+ * the logical page number and the physical page offset\r
+ * NOTE if it is not 0 this is not going to work for Stream \r
+ * file which doesn't support seek\r
+ */\r
+ public static final long FIRST_ALLOC_PAGE_NUMBER = 0L;\r
+ public static final long FIRST_ALLOC_PAGE_OFFSET = 0L;\r
+\r
+ // file status for persistent storage\r
+ private static final int FILE_DROPPED = 0x1;\r
+ private static final int FILE_COMMITTED_DROP = 0x2;\r
+\r
+ // recordId in this container can be reused when a page is reused.\r
+ private static final int FILE_REUSABLE_RECORDID = 0x8;\r
+\r
+ protected static final String SPACE_TRACE = \r
+ (SanityManager.DEBUG ? "SpaceTrace" : null);\r
+\r
+ FileContainer(BaseDataFileFactory factory) \r
+ {\r
+ dataFactory = factory;\r
+ pageCache = factory.getPageCache();\r
+ containerCache = factory.getContainerCache();\r
+ \r
+ initContainerHeader(true);\r
+ }\r
+\r
+ /**\r
+ Get information about space used by the container.\r
+ **/\r
+ public SpaceInfo getSpaceInfo(BaseContainerHandle handle)\r
+ throws StandardException\r
+ {\r
+ SpaceInformation spaceInfo;\r
+ synchronized(allocCache)\r
+ {\r
+ spaceInfo = \r
+ allocCache.getAllPageCounts(handle,firstAllocPageNumber);\r
+ }\r
+ spaceInfo.setPageSize(pageSize);\r
+ return spaceInfo;\r
+ }\r
+\r
+ /*\r
+ ** Methods of Cacheable\r
+ **\r
+ ** getIdentity() and clearIdentity() are implemented by BaseContainer\r
+ */\r
+\r
+ /**\r
+ Containers\r
+ */\r
+\r
+ /**\r
+ Open the container.\r
+\r
+ @return a valid object if the container was successfully opened, null if\r
+ it does not exist.\r
+\r
+ @exception StandardException Some problem in opening a container.\r
+\r
+ @see Cacheable#setIdentity\r
+ */\r
+ public Cacheable setIdentity(Object key) throws StandardException \r
+ {\r
+ return setIdent((ContainerKey) key);\r
+ }\r
+\r
+ /**\r
+ * Open the container.\r
+ * <p>\r
+ * Open the container with key "newIdentity".\r
+ * <p>\r
+ * should be same name as setIdentity but seems to cause method resolution \r
+ * ambiguities\r
+ *\r
+ * @exception StandardException Some problem in opening a container.\r
+ *\r
+ * @see Cacheable#setIdentity\r
+ **/\r
+ protected Cacheable setIdent(ContainerKey newIdentity) \r
+ throws StandardException \r
+ {\r
+ boolean ok = openContainer(newIdentity);\r
+\r
+ initializeLastInsertedPage(1);\r
+ lastUnfilledPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+\r
+ estimatedPageCount = -1;\r
+\r
+ if (ok) \r
+ {\r
+ // set up our identity.\r
+ // If we raise an exception after this we must clear our identity.\r
+ fillInIdentity(newIdentity);\r
+ return this;\r
+ }\r
+ else\r
+ { \r
+ return null;\r
+ }\r
+ }\r
+\r
+ public Cacheable createIdentity(Object key, Object createParameter) \r
+ throws StandardException \r
+ {\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ SanityManager.ASSERT(\r
+ !(key instanceof PageKey), "PageKey input to create container");\r
+ }\r
+\r
+ return createIdent((ContainerKey) key, createParameter);\r
+ }\r
+\r
+\r
+ // should be same name as createIdentity but seems to cause method \r
+ // resolution ambiguities\r
+ protected Cacheable createIdent(\r
+ ContainerKey newIdentity, \r
+ Object createParameter) \r
+ throws StandardException \r
+ {\r
+ // createParameter will be this object if this method is being called \r
+ // from itself to re-initialize the container (only for tempRAF)\r
+ // if createParameter == this, do not reinitialize the header, this\r
+ // object is not being reused for another container\r
+ if (createParameter != this) \r
+ {\r
+ initContainerHeader(true /* change to different container */);\r
+\r
+ if (createParameter != null && \r
+ (createParameter instanceof ByteArray))\r
+ {\r
+ // this is called during load tran, the create container\r
+ // Operation has a byte array created by logCreateContainerInfo\r
+ // which contains all the information necessary to recreate the\r
+ // container. Use that to recreate the container properties.\r
+\r
+ createInfoFromLog((ByteArray)createParameter);\r
+ }\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (createParameter != null &&\r
+ !(createParameter instanceof Properties))\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "Expecting a non-null createParameter to a " +\r
+ "Properties instead of " +\r
+ createParameter.getClass().getName());\r
+ }\r
+ }\r
+\r
+ createInfoFromProp((Properties)createParameter);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // we don't need to completely re-initialize the header\r
+ // just re-initialize the relavent fields\r
+ initContainerHeader(false);\r
+ }\r
+\r
+ if (initialPages > 1)\r
+ {\r
+ PreAllocThreshold = 0;\r
+ PreAllocSize = initialPages;\r
+ bulkIncreaseContainerSize = true;\r
+ }\r
+ else\r
+ {\r
+ PreAllocThreshold = PRE_ALLOC_THRESHOLD;\r
+ }\r
+\r
+ createContainer(newIdentity);\r
+\r
+ setDirty(true);\r
+\r
+ // set up our identity.\r
+ // If we raise an exception after this we must clear our identity.\r
+ fillInIdentity(newIdentity);\r
+\r
+ return this;\r
+ }\r
+\r
+ public void clearIdentity() \r
+ {\r
+\r
+ closeContainer();\r
+\r
+ initializeLastInsertedPage(1);\r
+ lastUnfilledPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+\r
+ canUpdate = false;\r
+ super.clearIdentity();\r
+ }\r
+\r
+ /**\r
+ We treat this container as dirty if it has the container file open.\r
+ @see Cacheable#isDirty\r
+ */\r
+ public boolean isDirty() \r
+ {\r
+ synchronized (this) \r
+ {\r
+ return isDirty;\r
+ }\r
+ }\r
+\r
+ public void preDirty(boolean preDirtyOn) \r
+ {\r
+ synchronized (this) \r
+ {\r
+ if (preDirtyOn)\r
+ {\r
+ // prevent the cleaner from cleaning this container or skipping\r
+ // over it until the operation which preDirtied it got a chance\r
+ // to do the change.\r
+ preDirty = true;\r
+ }\r
+ else\r
+ {\r
+ preDirty = false;\r
+ // if a cleaner is waiting on the dirty bit, wake it up\r
+ notifyAll();\r
+ }\r
+ }\r
+ }\r
+\r
+ protected void setDirty(boolean dirty)\r
+ {\r
+ synchronized(this) \r
+ {\r
+ preDirty = false;\r
+ isDirty = dirty;\r
+\r
+ // if a cleaner is waiting on the dirty bit, wake it up\r
+ notifyAll();\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Container creation, opening, and closing\r
+ */\r
+\r
+ /**\r
+ * Create a new container.\r
+ * <p>\r
+ * Create a new container, all references to identity must be through the\r
+ * passed in identity, this object will no identity until after this \r
+ * method returns.\r
+ *\r
+ * @exception StandardException Derby Standard error policy\r
+ **/\r
+ abstract void createContainer(ContainerKey newIdentity) \r
+ throws StandardException;\r
+ \r
+\r
+ /**\r
+ * Open a container.\r
+ * <p>\r
+ * Longer descrption of routine.\r
+ * <p>\r
+ * Open a container. Open the file that maps to this container, if the\r
+ * file does not exist then we assume the container was never created.\r
+ * If the file exists but we have trouble opening it then we throw some \r
+ * exception.\r
+ *\r
+ * <BR> MT - single thread required - Enforced by cache manager.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ abstract boolean openContainer(ContainerKey newIdentity) \r
+ throws StandardException;\r
+\r
+ abstract void closeContainer();\r
+\r
+ /**\r
+ * Drop Container.\r
+ * <p>\r
+ *\r
+ * @see Transaction#dropContainer\r
+ *\r
+ **/\r
+ protected void dropContainer(\r
+ LogInstant instant, \r
+ boolean isDropped)\r
+ {\r
+ synchronized(this)\r
+ {\r
+ setDroppedState(isDropped);\r
+ setDirty(true);\r
+ bumpContainerVersion(instant);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ increment the version by one and return the new version.\r
+\r
+ <BR> MT - caller must synchronized this in the same sync block that\r
+ modifies the container header.\r
+ */\r
+ protected final void bumpContainerVersion(LogInstant instant) \r
+ { \r
+ lastLogInstant = instant;\r
+ ++containerVersion;\r
+ }\r
+\r
+ protected long getContainerVersion()\r
+ {\r
+ // it is not really necessary to synchronized this because the only time the\r
+ // container version is looked at is during recovery, which is single\r
+ // threaded at the moment. Put it in an sync block anyway just in case\r
+ // some other people want to look at this for some bizarre reasons\r
+ synchronized(this)\r
+ {\r
+ return containerVersion;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Request the system properties associated with a container. \r
+ * <p>\r
+ * Request the value of properties that are associated with a container. \r
+ * The following properties can be requested:\r
+ * derby.storage.pageSize \r
+ * derby.storage.pageReservedSpace\r
+ * derby.storage.minimumRecordSize\r
+ * derby.storage.reusableRecordId\r
+ * derby.storage.initialPages\r
+ * <p>\r
+ * To get the value of a particular property add it to the property list,\r
+ * and on return the value of the property will be set to it's current \r
+ * value. For example:\r
+ *\r
+ * get_prop(ConglomerateController cc)\r
+ * {\r
+ * Properties prop = new Properties();\r
+ * prop.put("derby.storage.pageSize", "");\r
+ * cc.getContainerProperties(prop);\r
+ *\r
+ * System.out.println(\r
+ * "table's page size = " + \r
+ * prop.getProperty("derby.storage.pageSize");\r
+ * }\r
+ *\r
+ * @param prop Property list to fill in.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public void getContainerProperties(Properties prop)\r
+ throws StandardException\r
+ {\r
+ // derby.storage.pageSize\r
+ if (prop.getProperty(Property.PAGE_SIZE_PARAMETER) != null)\r
+ {\r
+ prop.put(\r
+ Property.PAGE_SIZE_PARAMETER, \r
+ Integer.toString(pageSize));\r
+ }\r
+\r
+ // derby.storage.minimumRecordSize\r
+ if (prop.getProperty(RawStoreFactory.MINIMUM_RECORD_SIZE_PARAMETER) != \r
+ null)\r
+ {\r
+ prop.put(\r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_PARAMETER, \r
+ Integer.toString(minimumRecordSize));\r
+ }\r
+\r
+ // derby.storage.pageReservedSpace\r
+ if (prop.getProperty(RawStoreFactory.PAGE_RESERVED_SPACE_PARAMETER) != \r
+ null)\r
+ {\r
+ prop.put(\r
+ RawStoreFactory.PAGE_RESERVED_SPACE_PARAMETER, \r
+ Integer.toString(spareSpace));\r
+ }\r
+\r
+ // derby.storage.reusableRecordId\r
+ if (prop.getProperty(RawStoreFactory.PAGE_REUSABLE_RECORD_ID) != null)\r
+ {\r
+ Boolean bool = new Boolean(isReusableRecordId());\r
+ prop.put(RawStoreFactory.PAGE_REUSABLE_RECORD_ID,\r
+ bool.toString());\r
+ }\r
+\r
+ // derby.storage.initialPages\r
+ if (prop.getProperty(RawStoreFactory.CONTAINER_INITIAL_PAGES) != null)\r
+ {\r
+ prop.put(RawStoreFactory.CONTAINER_INITIAL_PAGES,\r
+ Integer.toString(initialPages));\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ Read the container's header.\r
+\r
+ When this method is called, the embryonic page that is passed in must\r
+ have been read directly from the file or the input stream, even if the\r
+ alloc page may still be in cache. This is because a stubbify operation\r
+ only writes the stub to disk, it does not get rid of any stale page\r
+ from the page cache. So if it so happens that the stubbified container\r
+ object is aged out of the container cache but the first alloc page\r
+ hasn't, then when any stale page of this container wants to be written\r
+ out, the container needs to be reopened, which is when this routine is\r
+ called. We must not get the alloc page in cache because it may be\r
+ stale page and it may still say the container has not been dropped.\r
+\r
+ <BR> MT - single thread required - Enforced by caller.\r
+\r
+ @param epage the embryonic page to read the header from\r
+ @exception StandardException Derby Standard error policy\r
+ @exception IOException error in reading the header from file\r
+ */\r
+ protected void readHeader(byte[] epage)\r
+ throws IOException, StandardException\r
+ {\r
+ // read persistent container header into containerInfo\r
+ AllocPage.ReadContainerInfo(containerInfo, epage);\r
+\r
+ // initialize header from information stored in containerInfo\r
+ readHeaderFromArray(containerInfo);\r
+ }\r
+\r
+ // initialize header information so this container object can be safely\r
+ // reused as if this container object has just been new'ed\r
+ private void initContainerHeader(boolean changeContainer)\r
+ {\r
+ if (containerInfo == null)\r
+ containerInfo = new byte[CONTAINER_INFO_SIZE];\r
+\r
+ if (checksum == null)\r
+ checksum = new CRC32();\r
+ else\r
+ checksum.reset();\r
+\r
+ if (allocCache == null)\r
+ allocCache = new AllocationCache();\r
+ else\r
+ allocCache.reset();\r
+\r
+ if (changeContainer)\r
+ {\r
+ pageSize = 0;\r
+ spareSpace = 0;\r
+ minimumRecordSize = 0;\r
+ }\r
+\r
+ initialPages = 1;\r
+ firstAllocPageNumber = ContainerHandle.INVALID_PAGE_NUMBER; \r
+ firstAllocPageOffset = -1;\r
+ containerVersion = 0;\r
+ estimatedRowCount = 0;\r
+ reusableRecordIdSequenceNumber = 0;\r
+\r
+ setDroppedState(false);\r
+ setCommittedDropState(false);\r
+ setReusableRecordIdState(false);\r
+\r
+ // instance variables that are not stored on disk\r
+ lastLogInstant = null;\r
+\r
+ initializeLastInsertedPage(1);\r
+ lastUnfilledPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ estimatedPageCount = -1;\r
+\r
+ PreAllocThreshold = PRE_ALLOC_THRESHOLD;\r
+ PreAllocSize = DEFAULT_PRE_ALLOC_SIZE;\r
+ bulkIncreaseContainerSize = false;\r
+ }\r
+\r
+\r
+ /**\r
+ Read containerInfo from a byte array\r
+ The container Header array must be written by or of\r
+ the same format as put together by writeHeaderFromArray.\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ @exception IOException error in reading the header from file\r
+ */\r
+ private void readHeaderFromArray(byte[] a)\r
+ throws StandardException, IOException\r
+ {\r
+ ArrayInputStream inStream = new ArrayInputStream(a);\r
+\r
+ inStream.setLimit(CONTAINER_INFO_SIZE);\r
+ int fid = inStream.readInt();\r
+ if (fid != formatIdInteger)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNKNOWN_CONTAINER_FORMAT, getIdentity(), \r
+ new Long(fid));\r
+ }\r
+\r
+ int status = inStream.readInt();\r
+ pageSize = inStream.readInt();\r
+ spareSpace = inStream.readInt();\r
+ minimumRecordSize = inStream.readInt();\r
+ initialPages = inStream.readShort(); \r
+ PreAllocSize = inStream.readShort();\r
+ firstAllocPageNumber = inStream.readLong();\r
+ firstAllocPageOffset = inStream.readLong();\r
+ containerVersion = inStream.readLong();\r
+ estimatedRowCount = inStream.readLong();\r
+ reusableRecordIdSequenceNumber = inStream.readLong();\r
+ lastLogInstant = null;\r
+\r
+ if (PreAllocSize == 0) // pre 2.0, we don't store this.\r
+ PreAllocSize = DEFAULT_PRE_ALLOC_SIZE;\r
+\r
+ long spare3 = inStream.readLong(); // read spare long\r
+\r
+ // upgrade - if this is a container that was created before\r
+ // initialPages was stored, it will have a zero value. Set it to the\r
+ // default of 1.\r
+ if (initialPages == 0) \r
+ initialPages = 1;\r
+\r
+ // container read in from disk, reset preAllocation values\r
+ PreAllocThreshold = PRE_ALLOC_THRESHOLD;\r
+\r
+ // validate checksum\r
+ long onDiskChecksum = inStream.readLong();\r
+ checksum.reset();\r
+ checksum.update(a, 0, CONTAINER_INFO_SIZE - CHECKSUM_SIZE);\r
+\r
+ if (onDiskChecksum != checksum.getValue())\r
+ {\r
+ PageKey pk = new PageKey(identity, FIRST_ALLOC_PAGE_NUMBER);\r
+\r
+ throw dataFactory.markCorrupt\r
+ (StandardException.newException(\r
+ SQLState.FILE_BAD_CHECKSUM, \r
+ pk, \r
+ new Long(checksum.getValue()), \r
+ new Long(onDiskChecksum), \r
+ org.apache.derby.iapi.util.StringUtil.hexDump(a)));\r
+ }\r
+\r
+ allocCache.reset();\r
+\r
+ // set the in memory state\r
+ setDroppedState((status & FILE_DROPPED) != 0);\r
+ setCommittedDropState((status & FILE_COMMITTED_DROP) != 0);\r
+ setReusableRecordIdState((status & FILE_REUSABLE_RECORDID) != 0);\r
+ }\r
+\r
+\r
+ /**\r
+ Write the container header to a page array (the first allocation page)\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ @exception IOException error in writing the header to file\r
+ */\r
+ protected void writeHeader(byte[] pageData)\r
+ throws StandardException, IOException\r
+ {\r
+ // write out the current containerInfo in the borrowed space to byte\r
+ // array containerInfo\r
+ writeHeaderToArray(containerInfo);\r
+\r
+ AllocPage.WriteContainerInfo(containerInfo, pageData, false);\r
+ }\r
+\r
+ /**\r
+ Write the container header directly to file.\r
+\r
+ Subclasses that can writes the container header is expected to\r
+ manufacture a DataOutput stream which is used here.\r
+\r
+ <BR> MT - single thread required - Enforced by caller\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ @exception IOException error in writing the header to file\r
+ */\r
+ protected void writeHeader(StorageRandomAccessFile file,\r
+ boolean create, byte[] epage)\r
+ throws IOException, StandardException\r
+ {\r
+ // write out the current containerInfo in the borrowed space to byte\r
+ // array containerInfo\r
+ writeHeaderToArray(containerInfo);\r
+\r
+ // RESOLVE: get no wait on the page cache to see if allocation page is\r
+ // there, if so, use that instead of making a new array and a static\r
+ // function.\r
+\r
+ AllocPage.WriteContainerInfo(containerInfo, epage, create);\r
+ // now epage has the containerInfo written inside it\r
+\r
+ // force WAL - and check to see if database is corrupt or is frozen.\r
+ dataFactory.flush(lastLogInstant);\r
+ if (lastLogInstant != null)\r
+ lastLogInstant = null;\r
+\r
+ // write it out\r
+ dataFactory.writeInProgress();\r
+ try\r
+ {\r
+ writeAtOffset(file, epage, FIRST_ALLOC_PAGE_OFFSET);\r
+ }\r
+ finally\r
+ {\r
+ dataFactory.writeFinished();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Write a sequence of bytes at the given offset in a file. This method\r
+ * is not thread safe, so the caller must make sure that no other thread\r
+ * is performing operations that may change current position in the file.\r
+ *\r
+ * @param file the file to write to\r
+ * @param bytes the bytes to write\r
+ * @param offset the offset to start writing at\r
+ * @throws IOException if an I/O error occurs while writing\r
+ */\r
+ void writeAtOffset(StorageRandomAccessFile file, byte[] bytes, long offset)\r
+ throws IOException\r
+ {\r
+ file.seek(offset);\r
+ file.write(bytes);\r
+ }\r
+\r
+ /**\r
+ Get an embryonic page from the dataInput stream.\r
+\r
+ If fileData is not null, then the embyronic page will be read \r
+ in from the input stream (fileData), which is assumed to be \r
+ positioned at the beginning of the first allocation page.\r
+\r
+ if fileData is null, then just manufacture an array which\r
+ is the size of an embryonic page.\r
+\r
+ @exception IOException error in read the embryonic page from file\r
+ */\r
+ protected byte[] getEmbryonicPage(DataInput fileData) throws IOException\r
+ {\r
+ byte[] epage = new byte[AllocPage.MAX_BORROWED_SPACE];\r
+\r
+ if (fileData != null)\r
+ {\r
+ fileData.readFully(epage);\r
+ }\r
+ return epage;\r
+ }\r
+\r
+ /**\r
+ * Read an embryonic page (that is, a section of the first alloc page that\r
+ * is so large that we know all the borrowed space is included in it) from\r
+ * the specified offset in a {@code StorageRandomAccessFile}. This method\r
+ * is not thread safe, so the caller must make sure that no other thread\r
+ * is performing operations that may change current position in the file.\r
+ *\r
+ * @param file the file to read from\r
+ * @param offset where to start reading (normally\r
+ * {@code FileContainer.FIRST_ALLOC_PAGE_OFFSET})\r
+ * @return a byte array containing the embryonic page\r
+ * @throws IOException if an I/O error occurs while reading\r
+ */\r
+ byte[] getEmbryonicPage(StorageRandomAccessFile file, long offset)\r
+ throws IOException\r
+ {\r
+ file.seek(offset);\r
+ return getEmbryonicPage(file);\r
+ }\r
+\r
+ /**\r
+ Write containerInfo into a byte array\r
+ The container Header thus put together can be read by readHeaderFromArray.\r
+\r
+ @exception IOException error in writing the header\r
+ */\r
+ private void writeHeaderToArray(byte[] a) throws IOException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(a.length >= CONTAINER_INFO_SIZE,\r
+ "header won't fit in array");\r
+\r
+ ArrayOutputStream a_out = new ArrayOutputStream(a);\r
+ FormatIdOutputStream outStream = new FormatIdOutputStream(a_out);\r
+\r
+ int status = 0;\r
+ if (getDroppedState()) status |= FILE_DROPPED;\r
+ if (getCommittedDropState()) status |= FILE_COMMITTED_DROP;\r
+ if (isReusableRecordId()) status |= FILE_REUSABLE_RECORDID;\r
+\r
+ a_out.setPosition(0);\r
+ a_out.setLimit(CONTAINER_INFO_SIZE);\r
+ outStream.writeInt(formatIdInteger);\r
+ outStream.writeInt(status);\r
+ outStream.writeInt(pageSize);\r
+ outStream.writeInt(spareSpace);\r
+ outStream.writeInt(minimumRecordSize);\r
+ outStream.writeShort(initialPages);\r
+ outStream.writeShort(PreAllocSize); // write spare1\r
+ outStream.writeLong(firstAllocPageNumber);\r
+ outStream.writeLong(firstAllocPageOffset);\r
+ outStream.writeLong(containerVersion);\r
+ outStream.writeLong(estimatedRowCount);\r
+ outStream.writeLong(reusableRecordIdSequenceNumber);\r
+ outStream.writeLong(0); //Write spare3\r
+\r
+ checksum.reset();\r
+ checksum.update(a, 0, CONTAINER_INFO_SIZE - CHECKSUM_SIZE);\r
+\r
+ // write the checksum to the array\r
+ outStream.writeLong(checksum.getValue());\r
+\r
+ a_out.clearLimit();\r
+ }\r
+\r
+ /**\r
+ Log all information on the container creation necessary to recreate the\r
+ container during a load tran.\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected ByteArray logCreateContainerInfo() \r
+ throws StandardException\r
+ {\r
+ // just write out the whole container header\r
+ byte[] array = new byte[CONTAINER_INFO_SIZE];\r
+\r
+ try\r
+ {\r
+ writeHeaderToArray(array);\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNEXPECTED_EXCEPTION, ioe);\r
+ }\r
+\r
+ return new ByteArray(array);\r
+ }\r
+\r
+ /**\r
+ Set container properties from the passed in ByteArray, which is created\r
+ by logCreateContainerInfo. This information is used to recreate the\r
+ container during recovery load tran.\r
+\r
+ The following container properties are set:\r
+\r
+ pageSize\r
+ spareSpace\r
+ minimumRecordSize\r
+ isReusableRecordId\r
+ initialPages\r
+\r
+ */\r
+ private void createInfoFromLog(ByteArray byteArray) \r
+ throws StandardException \r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(byteArray != null,\r
+ "setCreateContainerInfoFromLog: ByteArray is null");\r
+ SanityManager.ASSERT(byteArray.getLength() == \r
+ CONTAINER_INFO_SIZE,\r
+ "setCreateContainerInfoFromLog: ByteArrays.length() != CONTAINER_INFO_SIZE");\r
+ }\r
+\r
+ byte[] array = byteArray.getArray();\r
+ \r
+ // now extract the relavent information from array - basically \r
+ // duplicate the code in readHeaderFromArray \r
+ ArrayInputStream inStream = new ArrayInputStream(array);\r
+\r
+ int status = 0;\r
+\r
+ try\r
+ { \r
+ inStream.setLimit(CONTAINER_INFO_SIZE);\r
+\r
+ int fid = inStream.readInt();\r
+ if (fid != formatIdInteger)\r
+ {\r
+ // RESOLVE: do something about this when we have > 1 container format\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNKNOWN_CONTAINER_FORMAT, \r
+ getIdentity(), new Long(fid));\r
+ }\r
+\r
+ status = inStream.readInt();\r
+ pageSize = inStream.readInt();\r
+ spareSpace = inStream.readInt();\r
+ minimumRecordSize = inStream.readInt();\r
+ initialPages = inStream.readShort(); \r
+\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNEXPECTED_EXCEPTION, ioe);\r
+ }\r
+\r
+ // set reusable record id property\r
+ setReusableRecordIdState((status & FILE_REUSABLE_RECORDID) != 0);\r
+\r
+ // sanity check to make sure we are not encoutering any\r
+ // dropped Container \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT((status & FILE_DROPPED) == 0 &&\r
+ (status & FILE_COMMITTED_DROP) == 0,\r
+ "cannot load a dropped container");\r
+ }\r
+ }\r
+\r
+ /**\r
+ Set container properties from the passed in createArgs. \r
+ The following container properties are set:\r
+\r
+ pageSize\r
+ spareSpace\r
+ minimumRecordSize\r
+ isReusableRecordId\r
+ initialPages\r
+\r
+ RESOLVE - in the future setting parameters should be overridable\r
+ by sub-class, e.g. one implementation of Container may require a\r
+ minimum page size of 4k.\r
+ */\r
+ private void createInfoFromProp(Properties createArgs)\r
+ throws StandardException\r
+ {\r
+ // Need a TransactionController to get database/service wide properties.\r
+ AccessFactory af = (AccessFactory)\r
+ Monitor.getServiceModule(dataFactory, AccessFactory.MODULE);\r
+\r
+ // RESOLVE: sku defectid 2014\r
+ TransactionController tc = \r
+ (af == null) ? \r
+ null : \r
+ af.getTransaction(\r
+ ContextService.getFactory().getCurrentContextManager());\r
+\r
+ pageSize = \r
+ PropertyUtil.getServiceInt(tc, createArgs,\r
+ Property.PAGE_SIZE_PARAMETER, \r
+ Limits.DB2_MIN_PAGE_SIZE, \r
+ Limits.DB2_MAX_PAGE_SIZE, \r
+ RawStoreFactory.PAGE_SIZE_DEFAULT); \r
+\r
+ // rather than throw error, just automatically set page size to \r
+ // default if bad value given.\r
+ if ((pageSize != 4096) &&\r
+ (pageSize != 8192) &&\r
+ (pageSize != 16384) &&\r
+ (pageSize != 32768))\r
+ {\r
+ pageSize= RawStoreFactory.PAGE_SIZE_DEFAULT;\r
+ }\r
+\r
+ spareSpace = \r
+ PropertyUtil.getServiceInt(tc, createArgs,\r
+ RawStoreFactory.PAGE_RESERVED_SPACE_PARAMETER, \r
+ 0, 100, 20);\r
+\r
+ PreAllocSize = \r
+ PropertyUtil.getServiceInt(tc, createArgs,\r
+ RawStoreFactory.PRE_ALLOCATE_PAGE,\r
+ MIN_PRE_ALLOC_SIZE,\r
+ MAX_PRE_ALLOC_SIZE, \r
+ DEFAULT_PRE_ALLOC_SIZE /* default */);\r
+\r
+ // RESOLVE - in the future, we will allow user to set minimumRecordSize\r
+ // to be larger than pageSize, when long rows are supported.\r
+ if (createArgs == null) {\r
+ // if the createArgs is null, then the following method call\r
+ // will get the system properties from the appropriete places.\r
+ // we want to make sure minimumRecrodSize is set to at least\r
+ // the default value MINIMUM_RECORD_SIZE_DEFAULT (12)\r
+ // as set in rawStoreFactory.\r
+ minimumRecordSize = \r
+ PropertyUtil.getServiceInt(tc,\r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_PARAMETER, \r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_DEFAULT, // this is different from the next call\r
+ // reserving 100 bytes for record/field headers\r
+ (pageSize * (1 - spareSpace/100) - 100), \r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_DEFAULT);\r
+ } else {\r
+ // if the createArgs is not null, then it has already been set\r
+ // by upper layer or create statement, then, we allow the minimum\r
+ // value of this to be MINIMUM_RECORD_SIZE_MINIMUM (1).\r
+ minimumRecordSize = \r
+ PropertyUtil.getServiceInt(tc, createArgs,\r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_PARAMETER, \r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_MINIMUM, // this is different from the last call\r
+ // reserving 100 bytes for record/field headers\r
+ (pageSize * (1 - spareSpace/100) - 100), \r
+ RawStoreFactory.MINIMUM_RECORD_SIZE_DEFAULT);\r
+ }\r
+\r
+ // For the following properties, do not check value set in global\r
+ // properties, we only listen to what access has to say about them.\r
+ //\r
+ // whether or not container's recordIds can be reused\r
+ // if container is to be created with a large number of pages\r
+ if (createArgs != null)\r
+ {\r
+ String reusableRecordIdParameter = \r
+ createArgs.getProperty(RawStoreFactory.PAGE_REUSABLE_RECORD_ID);\r
+ if (reusableRecordIdParameter != null)\r
+ { \r
+ Boolean reusableRecordId = new Boolean(reusableRecordIdParameter);\r
+ setReusableRecordIdState(reusableRecordId.booleanValue());\r
+ }\r
+\r
+ String containerInitialPageParameter =\r
+ createArgs.getProperty(RawStoreFactory.CONTAINER_INITIAL_PAGES);\r
+ if (containerInitialPageParameter != null)\r
+ {\r
+ initialPages = \r
+ Short.parseShort(containerInitialPageParameter);\r
+ if (initialPages > 1)\r
+ {\r
+ if (initialPages > RawStoreFactory.MAX_CONTAINER_INITIAL_PAGES)\r
+ initialPages = RawStoreFactory.MAX_CONTAINER_INITIAL_PAGES;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ */\r
+ protected boolean canUpdate() {\r
+ return canUpdate;\r
+ }\r
+\r
+ /**\r
+ Deallocate a page from the container. \r
+\r
+ @param handle the container handle doing the deallocation\r
+ @param page the page to be deallocated. It is latched upon entry and\r
+ will be unlatched by the caller of this function\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected void deallocatePage(BaseContainerHandle handle, BasePage page)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(page.isLatched(), "page is not latched");\r
+ SanityManager.ASSERT(page.getPageNumber() != FIRST_ALLOC_PAGE_NUMBER, \r
+ "cannot deallocate an alloc page");\r
+ }\r
+\r
+ long pnum = page.getPageNumber();\r
+\r
+ // dealloc the page from the alloc page\r
+ deallocatePagenum(handle, pnum);\r
+\r
+ // mark the page as deallocated. Page should not be touched after this\r
+ // the page latch is released by the BaseContainer upon return of this\r
+ // method. Regardless of whether this operation is successful or not,\r
+ // the page will be unlatched by BaseContainer.\r
+ page.deallocatePage();\r
+ \r
+ }\r
+\r
+ /** deallocate the page from the alloc page */\r
+ private void deallocatePagenum(BaseContainerHandle handle, long pnum)\r
+ throws StandardException\r
+ {\r
+ synchronized(allocCache)\r
+ {\r
+ long allocPageNum = allocCache.getAllocPageNumber(handle, pnum, firstAllocPageNumber);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (allocPageNum == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ allocCache.dumpAllocationCache();\r
+\r
+ if (allocPageNum == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ SanityManager.THROWASSERT(\r
+ "can't find alloc page for page number " +\r
+ pnum);\r
+ }\r
+ // get the alloc page to deallocate this pnum\r
+ AllocPage allocPage = (AllocPage)handle.getAllocPage(allocPageNum);\r
+ if (allocPage == null)\r
+ {\r
+ PageKey pkey = new PageKey(identity, allocPageNum);\r
+\r
+ throw StandardException.newException(\r
+ SQLState.FILE_NO_ALLOC_PAGE, pkey);\r
+ }\r
+\r
+ try\r
+ {\r
+ allocCache.invalidate(allocPage, allocPageNum); \r
+\r
+ // Unlatch alloc page. The page is protected by the dealloc\r
+ // lock. \r
+ allocPage.deallocatePage(handle, pnum);\r
+ }\r
+ finally\r
+ {\r
+ allocPage.unlatch();\r
+ }\r
+ }\r
+ // make sure this page gets looked at when someone needs a new page\r
+ if (pnum <= lastAllocatedPage) \r
+ {\r
+ lastAllocatedPage = pnum - 1;\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ Compress free space from container.\r
+\r
+ <BR> MT - thread aware - It is assumed that our caller (our super class)\r
+ has already arranged a logical lock on page allocation to only allow a\r
+ single thread through here.\r
+\r
+ Compressing free space is done in allocation page units, working\r
+ it's way from the end of the container to the beginning. Each\r
+ loop operates on the last allocation page in the container.\r
+\r
+ Freeing space in the container page involves 2 transactions, an\r
+ update to an allocation page, N data pages, and possibly the delete\r
+ of the allocation page.\r
+ The User Transaction (UT) initiated the compress call.\r
+ The Nested Top Transaction (NTT) is the transaction started by RawStore\r
+ inside the compress call. This NTT is committed before compress returns.\r
+ The NTT is used to access high traffic data structures such as the \r
+ AllocPage.\r
+\r
+ This is outline of the algorithm used in compressing the container.\r
+\r
+ Until a non free page is found loop, in each loop return to the OS\r
+ all space at the end of the container occupied by free pages, including\r
+ the allocation page itself if all of it's pages are free. \r
+ \r
+ 1) Find last 2 allocation pages in container (last if there is only one).\r
+ 2) invalidate the allocation information cached by the container.\r
+ Without the cache no page can be gotten from the container. Pages\r
+ already in the page cache are not affected. Thus by latching the \r
+ allocPage and invalidating the allocation cache, this NTT blocks out \r
+ all page gets from this container until it commits.\r
+ 3) the allocPage determines which pages can be released to the OS, \r
+ mark that in its data structure (the alloc extent). Mark the \r
+ contiguous block of nallocated/free pages at the end of the file\r
+ as unallocated. This change is associated with the NTT.\r
+ 4) The NTT calls the OS to deallocate the space from the file. Note\r
+ that the system can handle being booted and asked to get an allocated\r
+ page which is past end of file, it just extends the file automatically.\r
+ 5) If freeing all space on the alloc page, and there is more than one\r
+ alloc page, then free the alloc page - this requires an update to the \r
+ previous alloc page which the loop has kept latched also.\r
+ 6) if the last alloc page was deleted, restart loop at #1\r
+\r
+ All NTT latches are released before this routine returns.\r
+ If we use an NTT, the caller has to commit the NTT to release the\r
+ allocPage latch. If we don't use an NTT, the allocPage latch is released\r
+ as this routine returns.\r
+\r
+ @param ntt - the nested top transaction for the purpose of freeing space.\r
+ If ntt is null, use the user transaction for allocation.\r
+ #param allocHandle - the container handle opened by the ntt, \r
+ use this to latch the alloc page\r
+\r
+ @exception StandardException Standard Derby error policy \r
+ */\r
+ protected void compressContainer(\r
+ RawTransaction ntt,\r
+ BaseContainerHandle allocHandle)\r
+ throws StandardException \r
+ {\r
+ AllocPage alloc_page = null;\r
+ AllocPage prev_alloc_page = null;\r
+\r
+ if (firstAllocPageNumber == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ {\r
+ // no allocation pages in container, no work to do!\r
+ return;\r
+ }\r
+\r
+ \r
+ // make sure we don't execute redo recovery on any page\r
+ // which is getting truncated. At this point we have an exclusive\r
+ // table lock on the table, so after checkpoint no page change\r
+ // can happen between checkpoint log record and compress of space.\r
+ dataFactory.getRawStoreFactory().checkpoint();\r
+\r
+ // block the backup, If backup is already in progress wait \r
+ // for the backup to finish. Otherwise restore from the backup\r
+ // can start recovery at different checkpoint and possibly\r
+ // do redo on pages that are going to get truncated.\r
+ ntt.blockBackup(true);\r
+\r
+ try\r
+ {\r
+ synchronized(allocCache)\r
+ {\r
+ // loop until last 2 alloc pages are reached.\r
+ alloc_page = (AllocPage) \r
+ allocHandle.getAllocPage(firstAllocPageNumber);\r
+\r
+ while (!alloc_page.isLast())\r
+ {\r
+ if (prev_alloc_page != null)\r
+ {\r
+ // there are more than 2 alloc pages, unlatch the \r
+ // earliest one.\r
+ prev_alloc_page.unlatch();\r
+ }\r
+ prev_alloc_page = alloc_page;\r
+ alloc_page = null;\r
+\r
+ long nextAllocPageNumber = \r
+ prev_alloc_page.getNextAllocPageNumber();\r
+ long nextAllocPageOffset = \r
+ prev_alloc_page.getNextAllocPageOffset();\r
+\r
+ alloc_page = (AllocPage) \r
+ allocHandle.getAllocPage(nextAllocPageNumber);\r
+ }\r
+\r
+ // invalidate cache before compress changes cached information,\r
+ // while holding synchronization on cache and latch on \r
+ // allocation page. This should guarantee that only new info\r
+ // is seen after this operation completes.\r
+ allocCache.invalidate(); \r
+\r
+ // reset, as pages may not exist after compress\r
+ lastUnfilledPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+\r
+\r
+ alloc_page.compress(ntt, this);\r
+ }\r
+\r
+ }\r
+ finally\r
+ {\r
+ if (alloc_page != null)\r
+ {\r
+ alloc_page.unlatch();\r
+ alloc_page = null;\r
+ }\r
+ if (prev_alloc_page != null)\r
+ {\r
+ prev_alloc_page.unlatch();\r
+ prev_alloc_page = null;\r
+ }\r
+\r
+ // flush all changes to this file from cache.\r
+ flushAll();\r
+\r
+ // make sure all truncated pages are removed from the cache,\r
+ // as it will get confused in the future if we allocate the same\r
+ // page again, but find an existing copy of it in the cache - \r
+ // it expects to not find new pages in the cache. Could just\r
+ // get rid of truncated pages, iterface allows one page or\r
+ // all pages.\r
+ pageCache.discard(identity);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get the reusable RecordId sequence number for the container.\r
+ * @see BaseContainer#getReusableRecordIdSequenceNumber\r
+ * @return reusable RecordId sequence number for the container.\r
+ */\r
+ public final long getReusableRecordIdSequenceNumber() {\r
+ synchronized(this) {\r
+ return reusableRecordIdSequenceNumber;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Increment the reusable RecordId version sequence number.\r
+ */\r
+ protected final void incrementReusableRecordIdSequenceNumber()\r
+ {\r
+ final boolean readOnly = dataFactory.isReadOnly();\r
+ \r
+ synchronized (this) {\r
+ reusableRecordIdSequenceNumber++;\r
+ if (!readOnly)\r
+ {\r
+ isDirty = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ Create a new page in the container.\r
+\r
+ <BR> MT - thread aware - It is assumed that our caller (our super class)\r
+ has already arranged a logical lock on page allocation to only allow a\r
+ single thread through here.\r
+\r
+ Adding a new page involves 2 transactions and 2 pages. \r
+ The User Transaction (UT) initiated the addPage call and expects a\r
+ latched page (owns by the UT) to be returned.\r
+ The Nested Top Transaction (NTT) is the transaction started by RawStore\r
+ inside an addPage call. This NTT is committed before the page is\r
+ returned. The NTT is used to accessed high traffic data structure such\r
+ as the AllocPage.\r
+\r
+ This is outline of the algorithm used in adding a page:\r
+ 1) find or make an allocPage which can handle the addding of a new page.\r
+ Latch the allocPage with the NTT.\r
+ 2) invalidate the allocation information cached by the container.\r
+ Without the cache no page can be gotten from the container. Pages\r
+ already in the page cache is not affected. Thus by latching the \r
+ allocPage and invalidating the allocation cache, this NTT blocks out \r
+ all page gets from this container until it commits.\r
+ 3) the allocPage determines which page can be allocated, mark that in its\r
+ data structure (the alloc extent) and returns the page number of the\r
+ new page. This change is associated with the NTT.\r
+ 4) the NTT gets or creates the new page in the page cache (bypassing the\r
+ lookup of the allocPage since that is already latched by the NTT and\r
+ will deadlock).\r
+ 5) the NTT initializes the page (mark it is being a VALID page).\r
+ 6) the page latch is transfered to the UT from the NTT.\r
+ 7) the new page is returned, latched by UT\r
+\r
+ If we use an NTT, the caller has to commit the NTT to release the\r
+ allocPage latch. If we don't use an NTT, the allocPage latch is released\r
+ as this routine returns.\r
+\r
+ @param userHandle - the container handle opened by the user transaction, \r
+ use this to latch the new user page\r
+ @param ntt - the nested top transaction for the purpose of allocating the new page\r
+ If ntt is null, use the user transaction for allocation.\r
+ #param allocHandle - the container handle opened by the ntt, \r
+ use this to latch the alloc page\r
+\r
+ @exception StandardException Standard Derby error policy \r
+ */\r
+ protected BasePage newPage(BaseContainerHandle userHandle,\r
+ RawTransaction ntt,\r
+ BaseContainerHandle allocHandle,\r
+ boolean isOverflow) \r
+ throws StandardException \r
+ {\r
+ // NOTE: we are single threaded thru this method, see MT comment\r
+\r
+ boolean useNTT = (ntt != null);\r
+\r
+ // if ntt is null, use user transaction\r
+ if (!useNTT)\r
+ ntt = userHandle.getTransaction();\r
+\r
+ long lastPage; // last allocated page\r
+ long lastPreallocPage; // last pre-allcated page\r
+ long pageNumber; // the page number of the new page\r
+ PageKey pkey; // the identity of the new page\r
+ boolean reuse; // if true, we are trying to reuse a page\r
+\r
+ /* in case the page recommeded by allocPage is not committed yet, may\r
+ /* need to retry a couple of times */\r
+ boolean retry;\r
+ int numtries = 0;\r
+ long startSearch = lastAllocatedPage;\r
+\r
+ AllocPage allocPage = null; // the alloc page\r
+ BasePage page = null; // the new page\r
+\r
+ try\r
+ {\r
+ do\r
+ {\r
+ retry = false; // we don't expect we need to retry\r
+\r
+ synchronized(allocCache)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ ntt.getId().equals(\r
+ allocHandle.getTransaction().getId()));\r
+\r
+ if (useNTT)\r
+ SanityManager.ASSERT(\r
+ !ntt.getId().equals(\r
+ userHandle.getTransaction().getId()));\r
+ }\r
+\r
+ /* find an allocation page that can handle adding a new \r
+ * page.\r
+ *\r
+ * allocPage is unlatched when the ntt commits. The new \r
+ * page is initialized by the ntt but the latch is \r
+ * transfered to the user transaction before the allocPage \r
+ * is unlatched. The allocPage latch prevents almost any \r
+ * other reader or writer from finding the new page until \r
+ * the ntt is committed and the new page is latched by the\r
+ * user transaction.\r
+ *\r
+ * (If the page is being reused, it is possible for another\r
+ * xact which kept a handle on the reused page to find the \r
+ * page during the transfer UT -> NTT. If this unlikely \r
+ * even occurs and the transfer fails [see code relating \r
+ * to transfer below], we retry from the beginning.)\r
+ *\r
+ * After the NTT commits a reader (getNextPageNumber) may \r
+ * get the page number of the newly allocated page and it \r
+ * will wait for the new page and latch it when the user \r
+ * transaction commits, aborts or unlatches the new page. \r
+ * Whether the user transaction commits or aborts, the new \r
+ * page stay allocated.\r
+ *\r
+ * RESOLVE: before NTT rolls back (or commits) the latch is\r
+ * released. To repopulate the allocation cache, need to \r
+ * get either the container lock on add page, or get a per \r
+ * allocation page lock.\r
+ *\r
+ * This blocks all page read (getPage) from accessing this \r
+ * alloc page in this container until the alloc page is \r
+ * unlatched. Those who already have a page handle into \r
+ * this container are unaffected.\r
+ *\r
+ * In other words, allocation blocks out reader (of any \r
+ * page that is managed by this alloc page) by the latch \r
+ * on the allocation page.\r
+ *\r
+ * Note that write page can proceed as usual.\r
+ */\r
+ allocPage = \r
+ findAllocPageForAdd(allocHandle, ntt, startSearch);\r
+\r
+ allocCache.invalidate(allocPage, allocPage.getPageNumber());\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (allocPage == null)\r
+ allocCache.dumpAllocationCache();\r
+\r
+ SanityManager.ASSERT(allocPage != null,\r
+ "findAllocPageForAdd returned a null alloc page");\r
+ }\r
+\r
+ //\r
+ // get the next free page's number.\r
+ // for case 1, page number > lastPreallocPage\r
+ // for case 2, page number <= lastPage\r
+ // for case 3, lastPage < page number <= lastPreallocPage\r
+ //\r
+ pageNumber = allocPage.nextFreePageNumber(startSearch);\r
+\r
+ // need to distinguish between the following 3 cases:\r
+ // 1) the page has not been allocate or initalized.\r
+ // Create it in the page cache and sync it to disk.\r
+ // 2) the page is being re-allocated.\r
+ // We need to read it in to re-initialize it\r
+ // 3) the page has been preallocated.\r
+ // Create it in the page cache and don't sync it to disk\r
+ //\r
+ // first find out the current last initialized page and\r
+ // preallocated page before the new page is added\r
+ lastPage = allocPage.getLastPagenum();\r
+ lastPreallocPage = allocPage.getLastPreallocPagenum();\r
+\r
+ reuse = pageNumber <= lastPage;\r
+\r
+ // no address translation necessary\r
+ pkey = new PageKey(identity, pageNumber);\r
+\r
+\r
+ if (reuse)\r
+ {\r
+ // if re-useing a page, make sure the deallocLock on the new\r
+ // page is not held. We only need a zero duration lock on\r
+ // the new page because the allocPage is latched and this\r
+ // is the only thread which can be looking at this\r
+ // pageNumber.\r
+\r
+ RecordHandle deallocLock = BasePage.MakeRecordHandle(pkey,\r
+ RecordHandle.DEALLOCATE_PROTECTION_HANDLE);\r
+\r
+ if (!getDeallocLock(allocHandle, deallocLock,\r
+ false /* nowait */,\r
+ true /* zeroDuration */))\r
+ {\r
+\r
+ // The transaction which deallocated this page has not\r
+ // committed yet. Try going to some other page. If\r
+ // this is the first time we fail to get the dealloc\r
+ // lock, try from the beginning of the allocated page.\r
+ // If we already did that and still fail, keep going\r
+ // until we get a brand new page.\r
+ if (numtries == 0)\r
+ {\r
+ startSearch = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ lastAllocatedPage = pageNumber;\r
+ }\r
+ else // continue from where we were\r
+ startSearch = pageNumber;\r
+\r
+ numtries++;\r
+\r
+ // We have to unlatch the allocPage so that if that\r
+ // transaction rolls back, it won't deadlock with this\r
+ // transaction.\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+\r
+ retry = true;\r
+ }\r
+ else\r
+ {\r
+ // we got the lock, next time start from there\r
+ lastAllocatedPage = pageNumber;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // we got a new page, next time, start from beginning of\r
+ // the bit map again if we suspect there are some some\r
+ // deallocated pages\r
+ if (numtries > 0)\r
+ lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ else\r
+ lastAllocatedPage = pageNumber;\r
+ }\r
+\r
+ // Retry from the beginning if necessary.\r
+ if (retry)\r
+ continue;\r
+\r
+ // If we get past here must have (retry == false)\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(retry == false);\r
+ }\r
+\r
+ // Now we have verified that the allocPage is latched and we \r
+ // can get the zeroDuration deallocLock nowait. This means the\r
+ // transaction which freed the page has committed. Had that \r
+ // transaction aborted, we would have retried.\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // ASSERT lastPage <= lastPreallocPage\r
+ if (lastPage > lastPreallocPage)\r
+ {\r
+ SanityManager.THROWASSERT("last page " +\r
+ lastPage + " > lastPreallocPage " + \r
+ lastPreallocPage);\r
+ }\r
+ }\r
+\r
+ // No I/O at all if this new page is requested as part of a \r
+ // create and load statement or this new page is in a temporary\r
+ // container.\r
+ //\r
+ // In the former case, BaseContainer will allow the \r
+ // MODE_UNLOGGED bit to go thru to the nested top transaction \r
+ // alloc handle. In the later case, there is no nested top \r
+ // transaction and the alloc handle is the user handle, which \r
+ // is UNLOGGED.\r
+ boolean noIO = \r
+ (allocHandle.getMode() & ContainerHandle.MODE_UNLOGGED) ==\r
+ ContainerHandle.MODE_UNLOGGED;\r
+\r
+ // If we do not need the I/O (either because we are in a\r
+ // create_unlogged mode or we are dealing with a temp table), \r
+ // don't do any preallocation. Otherwise, see if we should be\r
+ // pre-Allocating page by now. We don't call it before\r
+ // nextFreePageNumber because finding a reusable page may be\r
+ // expensive and we don't want to start preAllocation unless \r
+ // there is no more reusable page. Unless we are called \r
+ // explicitly to bulk increase the container size in a preload \r
+ // or in a create container.\r
+ if (!noIO && \r
+ (bulkIncreaseContainerSize ||\r
+ (pageNumber > lastPreallocPage && \r
+ pageNumber > PreAllocThreshold)))\r
+ {\r
+ allocPage.preAllocatePage(\r
+ this, PreAllocThreshold, PreAllocSize);\r
+ }\r
+\r
+ // update last preAllocated Page, it may have been changed by \r
+ // the preAllocatePage call. We don't want to do the sync if \r
+ // preAllocatePage already took care of it.\r
+ lastPreallocPage = allocPage.getLastPreallocPagenum();\r
+ boolean prealloced = pageNumber <= lastPreallocPage;\r
+\r
+ // Argument to the create is an array of ints.\r
+ // The array is only used for new page creation or for creating\r
+ // a preallocated page, not for reuse.\r
+ // 0'th element is the page format\r
+ // 1'st element is whether or not to sync the page to disk\r
+ // 2'nd element is pagesize\r
+ // 3'rd element is spareSpace\r
+\r
+ PageCreationArgs createPageArgs = new PageCreationArgs(\r
+ StoredPage.FORMAT_NUMBER,\r
+ prealloced ? 0 : (noIO ? 0 : CachedPage.WRITE_SYNC),\r
+ pageSize,\r
+ spareSpace,\r
+ minimumRecordSize,\r
+ 0 /* containerInfoSize - unused for StoredPage */);\r
+\r
+ // RESOLVE: right now, there is no re-mapping of pages, so\r
+ // pageOffset = pageNumber*pageSize\r
+ long pageOffset = pageNumber * pageSize;\r
+\r
+ // initialize a new user page\r
+ // we first use the NTT to initialize the new page - in case the\r
+ // allocation failed, it is rolled back with the NTT.\r
+ // Later, we transfer the latch to the userHandle so it won't be\r
+ // released when the ntt commits\r
+\r
+ try\r
+ {\r
+ page = initPage(allocHandle, pkey, createPageArgs, pageOffset,\r
+ reuse, isOverflow);\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.DEBUG_PRINT("FileContainer",\r
+ "got exception from initPage:" +\r
+ "\nreuse = " + reuse +\r
+ "\nsyncFlag = " + createPageArgs.syncFlag +\r
+ "\nallocPage = " + allocPage\r
+ );\r
+ }\r
+ allocCache.dumpAllocationCache();\r
+\r
+ throw se;\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ page != null, "initPage returns null page");\r
+ SanityManager.ASSERT(\r
+ page.isLatched(), "initPage returns unlatched page");\r
+ }\r
+\r
+ // allocate the page in the allocation page bit map\r
+ allocPage.addPage(this, pageNumber, ntt, userHandle);\r
+\r
+ if (useNTT)\r
+ {\r
+ // transfer the page latch from NTT to UT.\r
+ //\r
+ // after the page is unlatched by NTT, it is still \r
+ // protected from being found by almost everybody else \r
+ // because the alloc page is still latched and the alloc \r
+ // cache is invalidated.\r
+ //\r
+ // However it is possible for the page to be \r
+ // found by threads who specifically ask for this \r
+ // pagenumber (e.g. HeapPostCommit).\r
+ // We may find that such a thread has latched the page. \r
+ // We shouldn't wait for it because we have the alloc page \r
+ // latch, and this could cause deadlock (e.g. \r
+ // HeapPostCommit might call removePage and this would wait\r
+ // on the alloc page).\r
+ //\r
+ // We may instead find that we can latch the page, but that\r
+ // another thread has managed to get hold of it during the \r
+ // transfer and either deallocated it or otherwise change it\r
+ // (add rows, delete rows etc.)\r
+ //\r
+ // Since this doesn't happen very often, we retry in these \r
+ // 2 cases (we give up the alloc page and page and we start\r
+ // this method from scratch).\r
+ //\r
+ // If the lock manager were changed to allow latches to be \r
+ // transferred between transactions, wouldn't need to \r
+ // unlatch to do the transfer, and would avoid having to \r
+ // retry in these cases (DERBY-2337).\r
+\r
+ page.unlatch();\r
+ page = null;\r
+\r
+ // need to find it in the cache again since unlatch also \r
+ // unkept the page from the cache\r
+ page = (BasePage)pageCache.find(pkey);\r
+ page = latchPage(\r
+ userHandle, page, \r
+ false /* don't wait, it might deadlock */);\r
+\r
+ if (page == null ||\r
+ // recordCount will only return true if there are no \r
+ // rows (including deleted rows)\r
+ page.recordCount() != 0 ||\r
+ page.getPageStatus() != BasePage.VALID_PAGE)\r
+ {\r
+ retry = true;\r
+ if (page != null)\r
+ {\r
+ page.unlatch();\r
+ page = null;\r
+ }\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+ }\r
+\r
+ }\r
+ // if ntt is null, no need to transfer. Page is latched by user\r
+ // transaction already. Will be no need to retry.\r
+ // the alloc page is unlatched in the finally block.\r
+ }\r
+ while (retry == true);\r
+\r
+ // At this point, should have a page suitable for returning\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(page.isLatched());\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ if (page != null)\r
+ page.unlatch();\r
+ page = null;\r
+\r
+ throw se; // rethrow error\r
+ }\r
+ finally\r
+ {\r
+ if (!useNTT && allocPage != null)\r
+ {\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+ }\r
+\r
+ // NTT is committed by the caller\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(page.isLatched());\r
+\r
+\r
+ // if bulkIncreaseContainerSize is set, that means this newPage call\r
+ // may have greatly expanded the container size due to preallocation.\r
+ // Regardless of how many page it actually created, reset preAllocSize\r
+ // to the default so we won't attempt to always preallocate 1000 pages\r
+ // at a time in the future.\r
+ if (bulkIncreaseContainerSize)\r
+ {\r
+ bulkIncreaseContainerSize = false;\r
+ PreAllocSize = DEFAULT_PRE_ALLOC_SIZE;\r
+ }\r
+\r
+ if (!isOverflow && page != null)\r
+ setLastInsertedPage(pageNumber);\r
+\r
+\r
+ // increase estimated page count - without any synchronization or\r
+ // logging, this is an estimate only\r
+ if (estimatedPageCount >= 0)\r
+ estimatedPageCount++;\r
+\r
+ if (!this.identity.equals(page.getPageId().getContainerId())) {\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.THROWASSERT(\r
+ "just created a new page from a different container"\r
+ + "\n this.identity = " + this.identity\r
+ + "\n page.getPageId().getContainerId() = " + \r
+ page.getPageId().getContainerId()\r
+ + "\n userHandle is: " + userHandle\r
+ + "\n allocHandle is: " + allocHandle\r
+ + "\n this container is: " + this);\r
+ }\r
+\r
+ throw StandardException.newException(\r
+ SQLState.DATA_DIFFERENT_CONTAINER, \r
+ this.identity, page.getPageId().getContainerId());\r
+ }\r
+\r
+ return page; // return the newly added page\r
+ }\r
+\r
+ protected void clearPreallocThreshold()\r
+ {\r
+ // start life with preallocated page if possible\r
+ PreAllocThreshold = 0;\r
+ }\r
+\r
+ protected void prepareForBulkLoad(BaseContainerHandle handle, int numPage)\r
+ {\r
+ clearPreallocThreshold();\r
+ RawTransaction tran = handle.getTransaction();\r
+\r
+ // find the last allocation page - do not invalidate the alloc cache,\r
+ // we don't want to prevent other people from reading or writing\r
+ // pages. \r
+ AllocPage allocPage = findLastAllocPage(handle, tran);\r
+\r
+ // preallocate numPages. Do whatever this allocPage can handle, if it\r
+ // is full, too bad. We don't guarentee that we will preallocate this\r
+ // many pages, we only promise to try.\r
+ if (allocPage != null)\r
+ {\r
+ allocPage.preAllocatePage(this, 0, numPage); \r
+ allocPage.unlatch(); \r
+ }\r
+ }\r
+\r
+ private boolean pageValid(BaseContainerHandle handle, long pagenum)\r
+ throws StandardException\r
+ {\r
+ boolean retval = false;\r
+\r
+ synchronized(allocCache)\r
+ {\r
+ if (pagenum <= allocCache.getLastPageNumber(handle, firstAllocPageNumber) && \r
+ allocCache.getPageStatus(handle, pagenum, firstAllocPageNumber) == AllocExtent.ALLOCATED_PAGE)\r
+ retval = true;\r
+ }\r
+\r
+ return retval;\r
+ }\r
+\r
+ protected long getLastPageNumber(BaseContainerHandle handle) \r
+ throws StandardException\r
+ {\r
+ long retval;\r
+ synchronized(allocCache)\r
+ {\r
+ // check if the first alloc page number is valid, it is invalid \r
+ // if some one attempts to access the container info before the \r
+ // first alloc page got created. One such case is online backup. \r
+ // If first alloc page itself is invalid, then there are no pages\r
+ // on the disk yet for this container, just return\r
+ // ContainerHandle.INVALID_PAGE_NUMBER, caller can decide what to\r
+ // do. \r
+ \r
+ if (firstAllocPageNumber == ContainerHandle.INVALID_PAGE_NUMBER) \r
+ {\r
+ retval = ContainerHandle.INVALID_PAGE_NUMBER;\r
+ }\r
+ else\r
+ {\r
+ retval = \r
+ allocCache.getLastPageNumber(handle, firstAllocPageNumber);\r
+ }\r
+ }\r
+ return retval;\r
+ }\r
+\r
+ /*\r
+ Find or allocate an allocation page which can handle adding a new page.\r
+ Return a latched allocPage.\r
+\r
+ <BR> MT - single thread required - called as part of add page\r
+ */\r
+ private AllocPage findAllocPageForAdd(BaseContainerHandle allocHandle,\r
+ RawTransaction ntt, long lastAllocatedPage)\r
+ throws StandardException\r
+ {\r
+ AllocPage allocPage = null;\r
+ AllocPage oldAllocPage = null; // in case we need to walk the alloc page chain\r
+ boolean success = false; // set this for clean up\r
+\r
+ try\r
+ {\r
+ if (firstAllocPageNumber == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ {\r
+ // make and return a latched new allocation page\r
+ allocPage = makeAllocPage(ntt, allocHandle, FIRST_ALLOC_PAGE_NUMBER,\r
+ FIRST_ALLOC_PAGE_OFFSET, CONTAINER_INFO_SIZE);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(firstAllocPageNumber == FIRST_ALLOC_PAGE_NUMBER,\r
+ "first Alloc Page number is still not set");\r
+ SanityManager.ASSERT(firstAllocPageOffset == FIRST_ALLOC_PAGE_OFFSET,\r
+ "first Alloc Page offset is still not set");\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // an allocation page already exist, go get it\r
+ allocPage = (AllocPage)allocHandle.getAllocPage(firstAllocPageNumber);\r
+ }\r
+\r
+ /* allocPage is latched by allocHandle */\r
+\r
+ if (!allocPage.canAddFreePage(lastAllocatedPage))\r
+ {\r
+ // allocPage cannot manage the addition of one more page, walk the\r
+ // alloc page chain till we find an allocPage that can\r
+ // RESOLVE: always start with the first page for now...\r
+\r
+ boolean found = false; // found an alloc page that can handle \r
+ // adding a new page\r
+\r
+ while(allocPage.isLast() != true)\r
+ {\r
+ long nextAllocPageNumber = allocPage.getNextAllocPageNumber();\r
+ long nextAllocPageOffset = allocPage.getNextAllocPageOffset();\r
+\r
+ // RESOLVE (future): chain this info to in memory structure so\r
+ // getAllocPage can find this alloc page\r
+\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+\r
+ // the nextAllocPage is stable once set - even though it is\r
+ // save to get the next page latch before releasing this\r
+ // allocPage.\r
+ allocPage = (AllocPage)allocHandle.getAllocPage(nextAllocPageNumber);\r
+\r
+ if (allocPage.canAddFreePage(lastAllocatedPage))\r
+ {\r
+ found = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (!found)\r
+ {\r
+ // allocPage is last and it is full\r
+ oldAllocPage = allocPage;\r
+ allocPage = null;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(oldAllocPage.getLastPagenum() ==\r
+ oldAllocPage.getMaxPagenum(),\r
+ "expect allocpage to be full but last pagenum != maxpagenum");\r
+\r
+ long newAllocPageNum = oldAllocPage.getMaxPagenum() + 1;\r
+ long newAllocPageOffset = newAllocPageNum; // no translation\r
+\r
+ allocPage = makeAllocPage(ntt, allocHandle,\r
+ newAllocPageNum,\r
+ newAllocPageOffset,\r
+ 0 /* no containerInfo */);\r
+\r
+ // this writes out the new alloc page and return a latched page\r
+ // nobody can find the new alloc page until oldAllocPage is unlatched.\r
+\r
+ // oldAllocPage is no longer the last alloc page, \r
+ // it has a pointer to the new last alloc page\r
+ oldAllocPage.chainNewAllocPage(allocHandle, newAllocPageNum, newAllocPageOffset);\r
+ oldAllocPage.unlatch();\r
+ oldAllocPage = null;\r
+ }\r
+ }\r
+\r
+ /* no error handling necessary */\r
+ success = true;\r
+ }\r
+ finally // unlatch allocation page if any error happened\r
+ {\r
+ if (!success)\r
+ {\r
+ if (oldAllocPage != null)\r
+ oldAllocPage.unlatch();\r
+\r
+ if (allocPage != null)\r
+ allocPage.unlatch();\r
+\r
+ allocPage = null;\r
+ }\r
+\r
+ // if success drop out of finally block\r
+ }\r
+\r
+ return allocPage;\r
+ }\r
+\r
+ /**\r
+ Find the last alloc page, returns null if no alloc page is found\r
+ */\r
+ private AllocPage findLastAllocPage(BaseContainerHandle handle,\r
+ RawTransaction tran)\r
+ {\r
+ AllocPage allocPage = null;\r
+ AllocPage oldAllocPage = null;\r
+\r
+ if (firstAllocPageNumber == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ return null;\r
+\r
+ try\r
+ {\r
+ allocPage = (AllocPage)handle.getAllocPage(firstAllocPageNumber);\r
+ while(!allocPage.isLast())\r
+ {\r
+ long nextAllocPageNumber = allocPage.getNextAllocPageNumber();\r
+ long nextAllocPageOffset = allocPage.getNextAllocPageOffset();\r
+\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+\r
+ allocPage = (AllocPage)handle.getAllocPage(nextAllocPageNumber);\r
+ }\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ if (allocPage != null)\r
+ allocPage.unlatch();\r
+ allocPage = null;\r
+ }\r
+\r
+ return allocPage;\r
+\r
+ }\r
+\r
+\r
+ /*\r
+ Make a new alloc page, latch it with the passed in container handle.\r
+ */\r
+ private AllocPage makeAllocPage(RawTransaction ntt, \r
+ BaseContainerHandle handle, \r
+ long pageNumber, \r
+ long pageOffset,\r
+ int containerInfoSize)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (containerInfoSize != 0 && \r
+ containerInfoSize != CONTAINER_INFO_SIZE)\r
+ SanityManager.THROWASSERT(\r
+ "expect 0 or " + CONTAINER_INFO_SIZE +\r
+ ", got " + containerInfoSize);\r
+\r
+ if (pageNumber != FIRST_ALLOC_PAGE_NUMBER &&\r
+ containerInfoSize != 0)\r
+ SanityManager.THROWASSERT(\r
+ "Not first alloc page but container info size "\r
+ + containerInfoSize);\r
+ }\r
+\r
+ // argument to the create is an array of ints\r
+ // 0'th element is the page format\r
+ // 1'st element is whether or not to sync the page to disk\r
+ // 2'nd element is the pagesize\r
+ // 3'rd element is spareSpace\r
+ // 4'th element is number of bytes to reserve for the container header\r
+ // 5'th element is the minimumRecordSize\r
+ // NOTE: the arg list here must match the one in allocPage\r
+\r
+ // No I/O at all if this new page is requested as part of a create\r
+ // and load statement or this new alloc page is in a temporary \r
+ // container.\r
+ // In the former case, BaseContainer will allow the MODE_UNLOGGED\r
+ // bit to go thru to the nested top transaction alloc handle.\r
+ // In the later case, there is no nested top transaction and the\r
+ // alloc handle is the user handle, which is UNLOGGED.\r
+\r
+ boolean noIO = (handle.getMode() & ContainerHandle.MODE_UNLOGGED) ==\r
+ ContainerHandle.MODE_UNLOGGED;\r
+\r
+ PageCreationArgs createAllocPageArgs = new PageCreationArgs(\r
+ AllocPage.FORMAT_NUMBER,\r
+ noIO ? 0 : CachedPage.WRITE_SYNC,\r
+ pageSize,\r
+ 0, // allocation page has no need for spare\r
+ minimumRecordSize,\r
+ containerInfoSize);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(SPACE_TRACE))\r
+ {\r
+ SanityManager.DEBUG(\r
+ SPACE_TRACE, "making new allocation page at " + pageNumber);\r
+ }\r
+ }\r
+\r
+ if (pageNumber == FIRST_ALLOC_PAGE_NUMBER)\r
+ {\r
+ // RESOLVE: make sure the following is true\r
+ // \r
+ // firstAllocPageNumber and Offset can be set and access without\r
+ // synchronization since the first allocation page is\r
+ // created as part of the container create, this value is set\r
+ // before any other transaction has a chance to open the container.\r
+ // Once set, the first allocation page does not move or change\r
+ // position \r
+ firstAllocPageNumber = pageNumber;\r
+ firstAllocPageOffset = pageOffset;\r
+\r
+ }\r
+\r
+ PageKey pkey = new PageKey(identity, pageNumber);\r
+\r
+ // return a latched new alloc page\r
+ return (AllocPage)initPage(handle, pkey, createAllocPageArgs, \r
+ pageOffset,\r
+ false, /* not reuse */\r
+ false /* not overflow */);\r
+ }\r
+\r
+ /**\r
+ Initialize a page \r
+\r
+ @return a latched page that has been initialized.\r
+\r
+ @param allochandle the contianer handle to initialize the page with - the ntt\r
+ @param pkey the page number of the page to be initialized\r
+ @param createArgs the arguments for page creation\r
+ @param reuse is true if we are reusing a page that has \r
+ already been initialized once\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected BasePage initPage(BaseContainerHandle allochandle, \r
+ PageKey pkey,\r
+ PageCreationArgs createArgs,\r
+ long pageOffset,\r
+ boolean reuse,\r
+ boolean overflow) throws StandardException\r
+ {\r
+ BasePage page = null;\r
+\r
+ boolean releasePage = true;\r
+\r
+ try\r
+ {\r
+ if (reuse) // read the page in first\r
+ {\r
+ // Cannot go thru the container handle because all read pages are blocked. \r
+ // do it underneath the handle and directly to the cache. \r
+ // Nobody can get thru becuase getPage will block at getting the alloc page.\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(SPACE_TRACE))\r
+ {\r
+ SanityManager.DEBUG(\r
+ SPACE_TRACE, "reusing page " + pkey);\r
+ }\r
+ }\r
+\r
+ page = (BasePage)pageCache.find(pkey);\r
+ if (page == null) // hmmm?\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_REUSE_PAGE_NOT_FOUND, pkey);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(SPACE_TRACE))\r
+ {\r
+ SanityManager.DEBUG(\r
+ SPACE_TRACE, "allocation new page " + pkey);\r
+ }\r
+ }\r
+\r
+ // a brand new page, initialize and a new page in cache\r
+ page = (BasePage) pageCache.create(pkey, createArgs);\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(page != null, "page Cache create return a null page");\r
+ }\r
+ releasePage = false;\r
+ page = latchPage(allochandle, page, true /* may need to wait, track3822 */);\r
+\r
+ if (page == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_NEW_PAGE_NOT_LATCHED, pkey);\r
+ } \r
+\r
+ // page is either brand new or is read from disk, in either case,\r
+ // it knows how to get itself initialized.\r
+ int initPageFlag = 0;\r
+ if (reuse) initPageFlag |= BasePage.INIT_PAGE_REUSE;\r
+ if (overflow) initPageFlag |= BasePage.INIT_PAGE_OVERFLOW;\r
+ if (reuse && isReusableRecordId())\r
+ initPageFlag |= BasePage.INIT_PAGE_REUSE_RECORDID;\r
+\r
+ page.initPage(initPageFlag, pageOffset);\r
+ page.setContainerRowCount(estimatedRowCount);\r
+\r
+ }\r
+ finally\r
+ {\r
+ if (releasePage && page != null)\r
+ {\r
+ // release the new page from cache if it errors \r
+ // out before the exclusive lock is set\r
+ pageCache.release((Cacheable)page);\r
+ page = null;\r
+ }\r
+ }\r
+\r
+ return page;\r
+ }\r
+\r
+\r
+ /**\r
+ Get a page in the container. \r
+\r
+ Get User page is the generic base routine for all user (client to raw\r
+ store) getPage. This routine coordinate with allocation/deallocation\r
+ to ensure that no page can be gotten from the container while page is\r
+ in the middle of being allocated or deallocated.\r
+ This routine latches the page.\r
+\r
+ @param handle the container handle\r
+ @param pageNumber the page number of the page to get\r
+ @param overflowOK if true then an overflow page is OK,\r
+ if false, then only non-overflow page is OK\r
+ @param wait if true then wait for a latch\r
+ @return the latched page\r
+\r
+ <BR> MT - thread safe\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ private BasePage getUserPage(BaseContainerHandle handle, long pageNumber,\r
+ boolean overflowOK, boolean wait)\r
+ throws StandardException\r
+ {\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ SanityManager.ASSERT(\r
+ pageNumber != FIRST_ALLOC_PAGE_NUMBER,\r
+ "getUserPage trying to get an alloc page, pageNumber = " + \r
+ pageNumber);\r
+\r
+ if (pageNumber < ContainerHandle.FIRST_PAGE_NUMBER)\r
+ SanityManager.THROWASSERT("pageNumber = " + pageNumber);\r
+ }\r
+\r
+ if (pageNumber < ContainerHandle.FIRST_PAGE_NUMBER)\r
+ return null;\r
+\r
+ if (getCommittedDropState()) // committed and dropped, cannot get a page\r
+ return null;\r
+\r
+ if (!pageValid(handle, pageNumber))\r
+ {\r
+ return null;\r
+ }\r
+\r
+ // RESOLVE: no translation!\r
+\r
+ PageKey pageSearch = new PageKey(identity, pageNumber);\r
+ BasePage page = (BasePage)pageCache.find(pageSearch);\r
+\r
+ if (page == null)\r
+ {\r
+ return page;\r
+ }\r
+\r
+ // latch the page\r
+ if (latchPage(handle,page,wait) == null)\r
+ {\r
+ // page was already released from cache\r
+ return null;\r
+ }\r
+\r
+ // double check for overflow and deallocated page\r
+ // a page that was valid before maybe invalid by now if it was\r
+ // deallocated in the interum.\r
+ // a page that is invalid can also become valid in the interim, but\r
+ // we do not handle that. The client must supply other locking\r
+ // mechanism to prevent that (an allocatino happenning where there are\r
+ // readers) if that is needed\r
+ if ((page.isOverflowPage() && !overflowOK) ||\r
+ (page.getPageStatus() != BasePage.VALID_PAGE))\r
+ {\r
+ // unlatch releases page from cache, see StoredPage.releaseExclusive()\r
+ page.unlatch();\r
+ page = null;\r
+ }\r
+\r
+ return page;\r
+ }\r
+\r
+ protected void trackUnfilledPage(long pagenumber, boolean unfilled)\r
+ {\r
+ if (!dataFactory.isReadOnly())\r
+ allocCache.trackUnfilledPage(pagenumber, unfilled);\r
+ }\r
+\r
+ /**\r
+ Get a valid (non-deallocated or free) page in the container.\r
+ Overflow page is OK. Resulting page is latched.\r
+\r
+ <BR> MT - thread safe\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected BasePage getPage(BaseContainerHandle handle, long pageNumber,\r
+ boolean wait)\r
+ throws StandardException\r
+ {\r
+ return getUserPage(handle, pageNumber, true /* overflow page OK */,\r
+ wait);\r
+ }\r
+\r
+\r
+ /**\r
+ Get any old page - turn off all validation\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected BasePage getAnyPage(BaseContainerHandle handle, long pageNumber) throws StandardException\r
+ {\r
+ // get AllocPage get a page without any validation (exception a\r
+ // committed dropped container)\r
+\r
+ if (getCommittedDropState()) // committed and dropped, cannot get a page\r
+ return null;\r
+\r
+ // make sure alloc cache has no stale info\r
+ synchronized(allocCache)\r
+ {\r
+ allocCache.invalidate();\r
+ }\r
+ \r
+ PageKey pageSearch = new PageKey(identity, pageNumber);\r
+ BasePage page = (BasePage) pageCache.find(pageSearch);\r
+\r
+ return page;\r
+ }\r
+\r
+ /**\r
+ * ReCreate a page for rollforward recovery. \r
+ * <p>\r
+ * During redo recovery it is possible for the system to try to redo\r
+ * the creation of a page (ie. going from non-existence to version 0).\r
+ * It first trys to read the page from disk, but a few different types\r
+ * of errors can occur:\r
+ * o the page does not exist at all on disk, this can happen during\r
+ * rollforward recovery applied to a backup where the file was\r
+ * copied and the page was added to the file during the time frame\r
+ * of the backup but after the physical file was copied.\r
+ * o space in the file exists, but it was never initalized. This\r
+ * can happen if you happen to crash at just the right moment during\r
+ * the allocation process. Also\r
+ * on some OS's it is possible to read from a part of the file that\r
+ * was not ever written - resulting in garbage from the store's \r
+ * point of view (often the result is all 0's). \r
+ *\r
+ * All these errors are easy to recover from as the system can easily \r
+ * create a version 0 from scratch and write it to disk.\r
+ *\r
+ * Because the system does not sync allocation of data pages, it is also\r
+ * possible at this point that whlie writing the version 0 to disk to \r
+ * create it we may encounter an out of disk space error (caught in this\r
+ * routine as a StandardException from the create() call. We can't \r
+ * recovery from this without help from outside, so the caught exception\r
+ * is nested and a new exception thrown which the recovery system will\r
+ * output to the user asking them to check their disk for space/errors.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected BasePage reCreatePageForRedoRecovery(\r
+ BaseContainerHandle handle,\r
+ int pageFormat,\r
+ long pageNumber,\r
+ long pageOffset)\r
+ throws StandardException\r
+ {\r
+ // recreating a page should be done only if are in the middle of \r
+ // rollforward recovery or if derby.storage.patchInitPageRecoverError \r
+ // is set to true.\r
+\r
+ //check if we are in rollforward recovery\r
+ boolean rollForwardRecovery = \r
+ ((RawTransaction)handle.getTransaction()).inRollForwardRecovery();\r
+\r
+ if (!rollForwardRecovery && !(PropertyUtil.getSystemBoolean(\r
+ RawStoreFactory.PATCH_INITPAGE_RECOVER_ERROR)))\r
+ {\r
+ return null;\r
+ }\r
+\r
+ // RESOLVE: first need to verify that the page is really NOT in the\r
+ // container!\r
+\r
+ // no address translation necessary\r
+ PageKey pkey = new PageKey(identity, pageNumber);\r
+\r
+ PageCreationArgs reCreatePageArgs;\r
+\r
+ if (pageFormat == StoredPage.FORMAT_NUMBER)\r
+ {\r
+ reCreatePageArgs = new PageCreationArgs(\r
+ pageFormat,\r
+ CachedPage.WRITE_SYNC,\r
+ pageSize,\r
+ spareSpace,\r
+ minimumRecordSize,\r
+ 0 /* containerInfoSize - unused for StoredPage */);\r
+ }\r
+ else if (pageFormat == AllocPage.FORMAT_NUMBER)\r
+ {\r
+\r
+ // only the first allocation page have borrowed space for the\r
+ // container info\r
+\r
+ int containerInfoSize = 0;\r
+ if (pageNumber == FIRST_ALLOC_PAGE_NUMBER)\r
+ {\r
+ containerInfoSize = CONTAINER_INFO_SIZE;\r
+ firstAllocPageNumber = pageNumber;\r
+ firstAllocPageOffset = pageOffset;\r
+ }\r
+\r
+ reCreatePageArgs = new PageCreationArgs(\r
+ pageFormat,\r
+ CachedPage.WRITE_SYNC,\r
+ pageSize,\r
+ 0, // allocation page has no need for spare\r
+ minimumRecordSize,\r
+ containerInfoSize);\r
+\r
+ }\r
+ else\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNKNOWN_PAGE_FORMAT, pkey);\r
+ }\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (SanityManager.DEBUG_ON("LoadTran"))\r
+ SanityManager.DEBUG_PRINT(\r
+ "Trace", "recreating page " + pkey + " for load tran");\r
+ }\r
+\r
+ // Can't just call initPage because that wants to log an initPage\r
+ // operation, whereas we are here because of an initPage operation in\r
+ // the log already.\r
+ BasePage page = null;\r
+ boolean releasePage = true;\r
+\r
+ try\r
+ {\r
+ try\r
+ {\r
+ // a brand new page, initialize a new page in cache\r
+ page = (BasePage) pageCache.create(pkey, reCreatePageArgs);\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_NEW_PAGE_DURING_RECOVERY, se, pkey);\r
+ }\r
+\r
+ if (page != null)\r
+ {\r
+ releasePage = false;\r
+ page = latchPage(handle, page, false /* never need to wait */);\r
+\r
+ if (page == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_NEW_PAGE_NOT_LATCHED, pkey);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.FILE_NEW_PAGE_DURING_RECOVERY, pkey);\r
+ }\r
+\r
+ }\r
+ finally\r
+ {\r
+ if (releasePage && page != null)\r
+ {\r
+ // release the new page from cache if it errors out before \r
+ // the exclusive lock is set error in roll forward recovery.\r
+ // , we are doomed anyway\r
+ pageCache.release((Cacheable)page);\r
+ page = null;\r
+ }\r
+ }\r
+\r
+ return page;\r
+\r
+ }\r
+\r
+\r
+ /** \r
+ Get an alloc page - only accessible to the raw store \r
+ (container and recovery)\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected BasePage getAllocPage(long pageNumber) throws StandardException \r
+ {\r
+ if (getCommittedDropState()) // committed and dropped, cannot get a page\r
+ return null;\r
+\r
+ PageKey pageSearch = new PageKey(identity, pageNumber);\r
+ BasePage page = (BasePage) pageCache.find(pageSearch);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (page == null)\r
+ SanityManager.THROWASSERT(\r
+ "getting a null alloc page page " + \r
+ getIdentity() + pageNumber);\r
+\r
+ if ( ! (page instanceof AllocPage))\r
+ SanityManager.THROWASSERT(\r
+ "trying to get a user page as an alloc page " + \r
+ getIdentity() + pageNumber); \r
+ }\r
+\r
+ // assuming that allocation page lives in the page cache...\r
+ return page;\r
+ }\r
+\r
+ /**\r
+ Get only a valid, non-overflow page. If page number is either invalid\r
+ or overflow, returns null\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected BasePage getHeadPage(BaseContainerHandle handle, long pageNumber,\r
+ boolean wait)\r
+ throws StandardException\r
+ {\r
+ return getUserPage(handle, pageNumber, false /* overflow not ok */,\r
+ wait);\r
+ }\r
+\r
+ /**\r
+ Get the first valid page in the container\r
+\r
+ @exception StandardException Derby Standard error policy\r
+ */\r
+ protected BasePage getFirstHeadPage(BaseContainerHandle handle, boolean wait)\r
+ throws StandardException\r
+ {\r
+ return getNextHeadPage(handle, ContainerHandle.FIRST_PAGE_NUMBER-1, wait);\r
+ }\r
+\r
+ /**\r
+ Get the next page in the container.\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected BasePage getNextHeadPage(BaseContainerHandle handle,\r
+ long pageNumber, boolean wait)\r
+ throws StandardException\r
+ {\r
+ long nextNumber;\r
+\r
+ while(true)\r
+ {\r
+ synchronized(allocCache)\r
+ {\r
+ // ask the cache for the next pagenumber\r
+ nextNumber = allocCache.getNextValidPage(handle, pageNumber, firstAllocPageNumber);\r
+ }\r
+\r
+ if (nextNumber == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ return null;\r
+\r
+ // optimistically go for the next page\r
+ BasePage p = getUserPage(handle, nextNumber,\r
+ false /* no overflow page*/, wait);\r
+ if (p != null)\r
+ return p;\r
+\r
+ pageNumber = nextNumber;\r
+ }\r
+ }\r
+\r
+\r
+ private BasePage getInsertablePage(BaseContainerHandle handle,\r
+ long pageNumber,\r
+ boolean wait,\r
+ boolean overflowOK)\r
+ throws StandardException\r
+ {\r
+ if (pageNumber == ContainerHandle.INVALID_PAGE_NUMBER)\r
+ return null;\r
+\r
+ BasePage p = getUserPage(handle, pageNumber, overflowOK, wait);\r
+ if (p != null)\r
+ {\r
+ // make sure the page is not too full\r
+ if (!p.allowInsert())\r
+ {\r
+ p.unlatch();\r
+ p = null;\r
+\r
+ // it is too full, make sure we are tracking it so we won't\r
+ // see it again.\r
+ allocCache.trackUnfilledPage(pageNumber, false);\r
+ }\r
+ }\r
+ /*\r
+ RESOLVE track 3757\r
+ Need to check if this fix resolves the bug.\r
+ This is commented out because we can't conclude here that this is not\r
+ a user page, it may just be that we failed to get a latch on the page.\r
+ In a high contention scenario this could cause alot of relatively empty\r
+ pages to not be considered for insert.\r
+ TODO\r
+ May be a good idea to move the trackUnfilledPage call below to some of\r
+ the lines in the getUserPage method.\r
+\r
+ else\r
+ {\r
+ // it is not a user page, make sure we are tracking its fillness so\r
+ // we won't consider it as a 1/2 filled page ever\r
+ allocCache.trackUnfilledPage(pageNumber, false);\r
+ }\r
+ */\r
+ return p;\r
+ }\r
+\r
+ /**\r
+ * Get candidate page to move a row for compressing the table.\r
+ * <p>\r
+ * The caller is moving rows from the end of the table toward the beginning,\r
+ * with the goal of freeing up a block of empty pages at the end of the\r
+ * container which can be returned to the OS.\r
+ * <p>\r
+ * On entry pageno will be latched by the caller. Only return pages with\r
+ * numbers below pageno. Attempting to return pageno will result in a\r
+ * latch/latch deadlock on the same thread.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected BasePage getPageForCompress(\r
+ BaseContainerHandle handle,\r
+ int flag,\r
+ long pageno)\r
+ throws StandardException\r
+ {\r
+ BasePage p = null;\r
+ boolean getLastInserted = \r
+ (flag & ContainerHandle.GET_PAGE_UNFILLED) == 0;\r
+\r
+ if (getLastInserted)\r
+ {\r
+ // There is nothing protecting lastInsertePage from being changed\r
+ // by another thread. Make a local copy.\r
+ long localLastInsertedPage = getLastInsertedPage();\r
+\r
+ if ((localLastInsertedPage < pageno) &&\r
+ (localLastInsertedPage != ContainerHandle.INVALID_PAGE_NUMBER))\r
+ {\r
+ // First try getting last inserted page.\r
+\r
+ p = getInsertablePage(\r
+ handle, \r
+ localLastInsertedPage,\r
+ true, /* wait */\r
+ false /* no overflow page */);\r
+\r
+ // if localLastInsertedPage is not an insertable page, \r
+ // don't waste time getting it again.\r
+ if (p == null)\r
+ {\r
+ // There is a slight possibility that lastUnfilledPage and\r
+ // lastInsertedPage will change between the if and the\r
+ // assignment. The worse that will happen is we lose the\r
+ // optimization. Don't want to slow down allocation by \r
+ // adding more synchronization.\r
+\r
+ if (localLastInsertedPage == getLastUnfilledPage())\r
+ setLastUnfilledPage(\r
+ ContainerHandle.INVALID_PAGE_NUMBER);\r
+\r
+ if (localLastInsertedPage == getLastInsertedPage())\r
+ setLastInsertedPage(\r
+ ContainerHandle.INVALID_PAGE_NUMBER);\r
+ }\r
+ }\r
+ }\r
+ else \r
+ {\r
+ // get a relatively unfilled page that is not the last Inserted page\r
+\r
+ long localLastUnfilledPage = getLastUnfilledPage();\r
+\r
+ if (localLastUnfilledPage == ContainerHandle.INVALID_PAGE_NUMBER ||\r
+ localLastUnfilledPage >= pageno ||\r
+ localLastUnfilledPage == getLastInsertedPage())\r
+ {\r
+ // get an unfilled page, searching from beginning of container.\r
+ localLastUnfilledPage = \r
+ getUnfilledPageNumber(handle, 0);\r
+ }\r
+\r
+ if ((localLastUnfilledPage != \r
+ ContainerHandle.INVALID_PAGE_NUMBER) &&\r
+ (localLastUnfilledPage < pageno))\r
+ {\r
+ p = getInsertablePage(\r
+ handle, localLastUnfilledPage, true, false);\r
+ }\r
+\r
+ // return this page for insert\r
+ if (p != null)\r
+ {\r
+ setLastUnfilledPage(localLastUnfilledPage);\r
+ setLastInsertedPage(localLastUnfilledPage);\r
+ }\r
+ }\r
+\r
+ return p;\r
+ }\r
+\r
+ /**\r
+ Get a potentially suitable page for insert and latch it.\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected BasePage getPageForInsert(BaseContainerHandle handle,\r
+ int flag)\r
+ throws StandardException\r
+ {\r
+ BasePage p = null;\r
+ boolean getLastInserted = (flag & ContainerHandle.GET_PAGE_UNFILLED) == 0;\r
+\r
+ if (getLastInserted)\r
+ {\r
+ // There is nothing protecting lastInsertePage from being changed\r
+ // by another thread. Make a local copy.\r
+ long localLastInsertedPage = getLastInsertedPage();\r
+\r
+ if (localLastInsertedPage != ContainerHandle.INVALID_PAGE_NUMBER)\r
+ {\r
+ // First try getting last allocated page, NOWAIT\r
+\r
+ p = getInsertablePage(handle, localLastInsertedPage,\r
+ false, /* wait */\r
+ false /* no overflow page */);\r
+\r
+ if (p == null)\r
+ {\r
+ // most likely we could not get the latch NOWAIT, try again\r
+ // with a new page, and tell the system to switch to \r
+ // multi-page mode.\r
+ /* switchToMultiInsertPageMode(handle); */\r
+\r
+ localLastInsertedPage = getLastInsertedPage();\r
+\r
+ p = getInsertablePage(handle, localLastInsertedPage,\r
+ true, /* wait */\r
+ false /* no overflow page */);\r
+ }\r
+ }\r
+\r
+ // if lastUnfilledPage is not an insertable page, don't waste time\r
+ // getting it again.\r
+ if (p == null)\r
+ {\r
+ // There is a slight possibility that lastUnfilledPage and\r
+ // lastInsertedPage will change between the if and the\r
+ // assignment. The worse that will happen is we lose the\r
+ // optimization. Don't want to slow down allocation by adding\r
+ // more synchronization.\r
+\r
+ if (localLastInsertedPage == getLastUnfilledPage())\r
+ setLastUnfilledPage(ContainerHandle.INVALID_PAGE_NUMBER);\r
+\r
+ if (localLastInsertedPage == getLastInsertedPage())\r
+ setLastInsertedPage(ContainerHandle.INVALID_PAGE_NUMBER);\r
+ }\r
+ }\r
+ else // get a relatively unfilled page that is not\r
+ { // the last Inserted page\r
+ long localLastUnfilledPage = getLastUnfilledPage();\r
+\r
+ if (localLastUnfilledPage == ContainerHandle.INVALID_PAGE_NUMBER ||\r
+ localLastUnfilledPage == getLastInsertedPage())\r
+ localLastUnfilledPage = getUnfilledPageNumber(handle, localLastUnfilledPage);\r
+\r
+ if (localLastUnfilledPage != ContainerHandle.INVALID_PAGE_NUMBER)\r
+ {\r
+ // try the last unfilled page we found - this could be\r
+ // different from lastInserted if the last unfilled one we\r
+ // found does not have enough space for the insert and the\r
+ // client wants to get a brand new page.\r
+ p = getInsertablePage(handle, localLastUnfilledPage, true, false);\r
+\r
+ // try again\r
+ if (p == null)\r
+ {\r
+ localLastUnfilledPage = getUnfilledPageNumber(handle, localLastUnfilledPage);\r
+ if (localLastUnfilledPage != ContainerHandle.INVALID_PAGE_NUMBER)\r
+ {\r
+ p = getInsertablePage(handle, localLastUnfilledPage, true,\r
+ false);\r
+ }\r
+ }\r
+ }\r
+\r
+ // return this page for insert\r
+ if (p != null)\r
+ {\r
+ setLastUnfilledPage(localLastUnfilledPage);\r
+ setLastInsertedPage(localLastUnfilledPage);\r
+ }\r
+ }\r
+\r
+ return p;\r
+\r
+ }\r
+\r
+\r
+ /** \r
+ * Get a latched page. Incase of backup page Latch is necessary to \r
+ * prevent modification to the page when it is being written to the backup.\r
+ * Backup process relies on latches to get consistent snap\r
+ * shot of the page , user level table/page/row locks are NOT \r
+ * acquired by the online backup mechanism.\r
+ *\r
+ * @param handle the container handle used to latch the page\r
+ * @param pageNumber the page number of the page to get\r
+ * @return the latched page\r
+ * @exception StandardException Standard Derby error policy\r
+ */\r
+ protected BasePage getLatchedPage(BaseContainerHandle handle, \r
+ long pageNumber) \r
+ throws StandardException \r
+ {\r
+ PageKey pageKey = new PageKey(identity, pageNumber);\r
+ BasePage page = (BasePage) pageCache.find(pageKey);\r
+ \r
+ if (SanityManager.DEBUG){\r
+ SanityManager.ASSERT(page != null, "page is not found :" + pageKey);\r
+ }\r
+ \r
+ // latch the page\r
+ page = latchPage(handle, page, true);\r
+ \r
+ if (SanityManager.DEBUG){\r
+ SanityManager.ASSERT(page.isLatched(), "page is not latched:" + pageKey);\r
+ }\r
+\r
+ return page;\r
+ }\r
+\r
+ \r
+\r
+ private long getUnfilledPageNumber(BaseContainerHandle handle, long pagenum)\r
+ throws StandardException\r
+ {\r
+ synchronized(allocCache)\r
+ {\r
+ return allocCache.\r
+ getUnfilledPageNumber(handle, firstAllocPageNumber, pagenum);\r
+ }\r
+ } \r
+\r
+ /*\r
+ Cost estimates\r
+ */\r
+ /**\r
+ <BR>MT - this routine is NOT MT-safe and clients don't need to provide\r
+ synchronization.\r
+\r
+ @see ContainerHandle#getEstimatedRowCount\r
+ */\r
+ public long getEstimatedRowCount(int flag)\r
+ {\r
+ return estimatedRowCount;\r
+ }\r
+\r
+ /**\r
+ @see ContainerHandle#setEstimatedRowCount\r
+ */\r
+ public void setEstimatedRowCount(long count, int flag)\r
+ {\r
+ boolean readOnly = dataFactory.isReadOnly();\r
+\r
+ synchronized(this)\r
+ {\r
+ estimatedRowCount = count;\r
+\r
+ if (!readOnly)\r
+ isDirty = true;\r
+ }\r
+ }\r
+\r
+ /**\r
+ Update estimated row count by page as it leaves the cache.\r
+ The estimated row count is updated without logging!\r
+ */\r
+ protected void updateEstimatedRowCount(int delta)\r
+ {\r
+ boolean readOnly = dataFactory.isReadOnly();\r
+\r
+ synchronized(this)\r
+ {\r
+ estimatedRowCount += delta;\r
+ if (estimatedRowCount < 0)\r
+ estimatedRowCount = 0;\r
+\r
+ // mark the container as dirty without bumping the container\r
+ // version because row count changes are not logged.\r
+ if (!readOnly)\r
+ isDirty = true;\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ @see ContainerHandle#getEstimatedPageCount\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ public long getEstimatedPageCount(BaseContainerHandle handle, int flag)\r
+ throws StandardException \r
+ {\r
+ // page count is set once per container materialization in cache\r
+\r
+ if (estimatedPageCount < 0)\r
+ {\r
+ synchronized(allocCache)\r
+ {\r
+ estimatedPageCount = \r
+ allocCache.getEstimatedPageCount(handle, firstAllocPageNumber);\r
+ }\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(estimatedPageCount >= 0,\r
+ "AllocCache returns negatie estimatedPageCount");\r
+\r
+ return estimatedPageCount;\r
+ }\r
+\r
+ /*\r
+ ** Methods used solely by StoredPage\r
+ */\r
+\r
+ /**\r
+ Read a page into the supplied array.\r
+\r
+ <BR> MT - thread safe\r
+ @exception IOException error reading page\r
+ @exception StandardException standard Derby error message\r
+ */\r
+ protected abstract void readPage(long pageNumber, byte[] pageData)\r
+ throws IOException, StandardException;\r
+ \r
+\r
+ /**\r
+ Write a page from the supplied array.\r
+\r
+ <BR> MT - thread safe\r
+ @exception IOException error writing page\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected abstract void writePage(long pageNumber, byte[] pageData, boolean syncPage) \r
+ throws IOException, StandardException;\r
+\r
+ /*\r
+ * Encryption/decryption\r
+ */\r
+ /**\r
+ Decrypts a page\r
+\r
+ <BR>MT - MT safe.\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected void decryptPage(byte[] pageData, int pageSize)\r
+ throws StandardException\r
+ {\r
+ // because all our page header looks identical, the \r
+ // checksum is moved to the front so that it will hopefully\r
+ // encrypt differently from page to page\r
+ synchronized(this)\r
+ {\r
+ if (encryptionBuffer == null || encryptionBuffer.length < pageSize)\r
+ encryptionBuffer = new byte[pageSize];\r
+\r
+ int len = dataFactory.decrypt(pageData, 0, pageSize,\r
+ encryptionBuffer, 0);\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(len == pageSize,\r
+ "Encrypted page length != page length");\r
+\r
+ // put the checksum where it belongs\r
+ System.arraycopy(encryptionBuffer, 8, pageData, 0, pageSize-8);\r
+ System.arraycopy(encryptionBuffer, 0, pageData, pageSize-8, 8);\r
+ }\r
+ }\r
+\r
+ /**\r
+ Encrypts a page.\r
+\r
+ <BR> MT - not safe, call within synchronized block and only use the\r
+ returned byte array withing synchronized block. \r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ protected byte[] encryptPage(byte[] pageData, \r
+ int pageSize, \r
+ byte[] encryptionBuffer,\r
+ boolean newEngine)\r
+ throws StandardException\r
+ {\r
+ // because all our page header looks identical, move the\r
+ // checksum to the front so that it will hopefully encrypt\r
+ // differently from page to page\r
+\r
+ System.arraycopy(pageData, pageSize-8, encryptionBuffer, 0, 8);\r
+ System.arraycopy(pageData, 0, encryptionBuffer, 8, pageSize-8);\r
+\r
+ int len = dataFactory.encrypt(encryptionBuffer, 0, pageSize,\r
+ encryptionBuffer, 0, newEngine);\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(len == pageSize,\r
+ "Encrypted page length != page length");\r
+\r
+ return encryptionBuffer;\r
+ }\r
+\r
+\r
+ /** \r
+ * Get encryption buffer.\r
+ * MT - not safe, call within synchronized block and only use the\r
+ * returned byte array withing synchronized block. \r
+ * @return byte array to be used for encryping a page.\r
+ */\r
+ protected byte[] getEncryptionBuffer() {\r
+\r
+ if (encryptionBuffer == null || encryptionBuffer.length < pageSize)\r
+ encryptionBuffer = new byte[pageSize];\r
+ return encryptionBuffer;\r
+ }\r
+ \r
+ \r
+\r
+ /*\r
+ * page preallocation\r
+ */\r
+\r
+ /**\r
+ preAllocate writes out the preallocated pages to disk if necessary.\r
+\r
+ <BR>Make sure the container is large enough and the\r
+ pages are well formatted. The only reason to do this is to save some\r
+ I/O during page initialization. Once the initPage log record is\r
+ written, it is expected that the page really do exist and is well\r
+ formed or recovery will fail. However, we can gain some performance by\r
+ writing a bunch of pages at a time rather than one at a time.\r
+\r
+ <BR>If it doesn't make sense for the the implementation to have \r
+ pre-allocation, just return 0. \r
+\r
+ <BR>If the container is not being logged, don't actually do anything,\r
+ just return 0. \r
+\r
+ @return number of successfully preallocated page, or 0 if\r
+ no page has been preallocated\r
+\r
+ @param lastPreallocPagenum the last preallocated page number as known\r
+ by the allocation page\r
+ @param preAllocSize try to preallocate this page number of pages.\r
+ Since only the container knows how many pages are actually on\r
+ disk, it may determine that certain number of pages that the\r
+ allocation page thinks need to be preallocated is already\r
+ allocated, in those case, act as if the preallocation is\r
+ successful.\r
+ */\r
+ protected abstract int preAllocate(long lastPreallocPagenum, int preAllocSize);\r
+\r
+ /**\r
+ Preallocate the pages - actually doing it, called by subclass only\r
+ */\r
+ protected int doPreAllocatePages(long lastPreallocPagenum,\r
+ int preAllocSize)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(!dataFactory.isReadOnly(), \r
+ "how can we be Preallocating pages in a read only database?");\r
+\r
+ // initialize and a new page in cache\r
+ PageCreationArgs createArgs = new PageCreationArgs(\r
+ StoredPage.FORMAT_NUMBER, // default is a stored page\r
+ CachedPage.WRITE_NO_SYNC, // write it but no sync\r
+ pageSize,\r
+ spareSpace,\r
+ minimumRecordSize,\r
+ 0 /* containerInfoSize - unused for StoredPage */);\r
+\r
+ StoredPage page = new StoredPage();\r
+ page.setFactory(dataFactory);\r
+\r
+ boolean error = false;\r
+ int count = 0;\r
+\r
+ while(count < preAllocSize)\r
+ {\r
+ PageKey pkey = new PageKey(identity, \r
+ lastPreallocPagenum+count+1);\r
+ try\r
+ {\r
+ // create Identity will do a writePage\r
+ page.createIdentity(pkey, createArgs);\r
+\r
+ // if create identity somehow failed to do a write page\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(!page.isDirty(),\r
+ "create identity failed to do a write page");\r
+\r
+ page.clearIdentity(); // ready the page for the next loop \r
+\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ // if something went wrong, stop and return how many we did\r
+ // successfully \r
+ error = true;\r
+ }\r
+\r
+ if (error)\r
+ break;\r
+\r
+ count++;\r
+ }\r
+\r
+ return count;\r
+ }\r
+\r
+ protected int getPageSize() {\r
+ return pageSize;\r
+ }\r
+ protected int getSpareSpace() {\r
+ return spareSpace;\r
+ }\r
+ protected int getMinimumRecordSize() {\r
+ return minimumRecordSize;\r
+ }\r
+\r
+ private synchronized void switchToMultiInsertPageMode(\r
+ BaseContainerHandle handle)\r
+ throws StandardException\r
+ {\r
+ if (lastInsertedPage.length == 1)\r
+ {\r
+ long last = lastInsertedPage[0];\r
+\r
+ lastInsertedPage = new long[4];\r
+ lastInsertedPage[0] = last;\r
+\r
+ for (int i = 3; i > 0; i--)\r
+ {\r
+ Page page = addPage(handle, false);\r
+ lastInsertedPage[i] = page.getPageNumber();\r
+ page.unlatch();\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ * Setting and getting lastInserted Page and lastUnfilledPage in a thead\r
+ * safe manner. \r
+ */\r
+ private synchronized long getLastInsertedPage()\r
+ {\r
+ if (lastInsertedPage.length == 1)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(lastInsertedPage_index == 0);\r
+\r
+ // optimize the usual case where no concurrent insert has kicked us\r
+ // into multi-page mode - ie. only ONE last page. \r
+ return(lastInsertedPage[0]);\r
+ }\r
+ else\r
+ {\r
+ long ret = lastInsertedPage[lastInsertedPage_index++];\r
+\r
+ if (lastInsertedPage_index > (lastInsertedPage.length - 1))\r
+ {\r
+ lastInsertedPage_index = 0;\r
+ }\r
+\r
+ return(ret);\r
+ }\r
+ }\r
+\r
+ private synchronized long getLastUnfilledPage()\r
+ {\r
+ return lastUnfilledPage;\r
+ }\r
+\r
+ private synchronized void initializeLastInsertedPage(int size)\r
+ {\r
+ lastInsertedPage = new long[size];\r
+\r
+ for (int i = lastInsertedPage.length - 1; i >= 0; i--)\r
+ lastInsertedPage[i] = ContainerHandle.INVALID_PAGE_NUMBER;\r
+\r
+ lastInsertedPage_index = 0;\r
+ }\r
+\r
+ private synchronized void setLastInsertedPage(long val)\r
+ {\r
+ lastInsertedPage[lastInsertedPage_index] = val;\r
+ }\r
+\r
+ private synchronized void setLastUnfilledPage(long val)\r
+ {\r
+ lastUnfilledPage = val;\r
+ }\r
+\r
+\r
+\r
+ /*\r
+ ** Hide our super-classes methods to ensure that cache management\r
+ ** is correct when the container is obtained and release.\r
+ */\r
+\r
+ /**\r
+ The container is kept by the find() in File.openContainer. \r
+ */\r
+ protected void letGo(BaseContainerHandle handle) {\r
+ super.letGo(handle);\r
+\r
+ containerCache.release(this);\r
+ }\r
+\r
+ protected BasePage latchPage(BaseContainerHandle handle, BasePage foundPage, boolean wait)\r
+ throws StandardException {\r
+\r
+ if (foundPage == null)\r
+ return null;\r
+\r
+ BasePage ret = super.latchPage(handle, foundPage, wait);\r
+ if (ret == null) {\r
+ // page is still cached\r
+ pageCache.release((Cacheable) foundPage);\r
+ }\r
+ return ret;\r
+ }\r
+ \r
+\r
+\r
+ /**\r
+ * backup the container.\r
+ * \r
+ * @param handle the container handle.\r
+ * @param backupLocation location of the backup container. \r
+ * @exception StandardException Standard Derby error policy \r
+ */\r
+ protected abstract void backupContainer(BaseContainerHandle handle, \r
+ String backupLocation)\r
+ throws StandardException;\r
+}\r