| /* |
| |
| Derby - Class org.apache.derby.impl.store.access.btree.BTreeController |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.store.access.btree; |
| |
| import java.util.Properties; |
| |
| import org.apache.derby.iapi.reference.SQLState; |
| |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo; |
| import org.apache.derby.iapi.store.access.conglomerate.TransactionManager; |
| import org.apache.derby.iapi.store.access.AccessFactoryGlobals; |
| import org.apache.derby.iapi.store.access.ConglomerateController; |
| import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo; |
| import org.apache.derby.iapi.store.access.RowLocationRetRowSource; |
| import org.apache.derby.iapi.store.access.RowUtil; |
| import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo; |
| import org.apache.derby.iapi.store.access.TransactionController; |
| |
| import org.apache.derby.iapi.store.raw.ContainerHandle; |
| import org.apache.derby.iapi.store.raw.FetchDescriptor; |
| import org.apache.derby.iapi.store.raw.LockingPolicy; |
| import org.apache.derby.iapi.store.raw.Page; |
| import org.apache.derby.iapi.store.raw.RecordHandle; |
| import org.apache.derby.iapi.store.raw.Transaction; |
| |
| import org.apache.derby.iapi.types.DataValueDescriptor; |
| |
| import org.apache.derby.iapi.types.RowLocation; |
| |
| import org.apache.derby.iapi.services.io.FormatableBitSet; |
| import org.apache.derby.impl.store.access.conglomerate.ConglomerateUtil; |
| |
| /** |
| |
| A b-tree controller corresponds to an instance of an open b-tree conglomerate. |
| <P> |
| <B>Concurrency Notes</B> |
| <P> |
| The concurrency rules are derived from OpenBTree. |
| <P> |
| @see OpenBTree |
| |
| **/ |
| |
| public class BTreeController extends OpenBTree implements ConglomerateController |
| { |
| |
| transient DataValueDescriptor[] scratch_template = null; |
| |
| /** |
| * Whether to get lock on the row being inserted, usually this lock |
| * has already been gotten when the row was inserted into the base table. |
| **/ |
| boolean get_insert_row_lock; |
| |
| //constants for the status of dupicate checking |
| private static final int NO_MATCH = 0; |
| private static final int MATCH_FOUND = 1; |
| private static final int RESCAN_REQUIRED = 2; |
| |
| /* Constructors: */ |
| |
| public BTreeController() |
| { |
| } |
| |
| /* |
| ** private Methods of BTreeController |
| */ |
| |
| /** |
| * Attempt to reclaim committed deleted rows from the page. |
| * <p> |
| * Get exclusive latch on page, and then loop backward through |
| * page searching for deleted rows which are committed. The routine |
| * assumes that it is called from a transaction which cannot have |
| * deleted any rows on the page. For each deleted row on the page |
| * it attempts to get an exclusive lock on the deleted row, NOWAIT. |
| * If it succeeds, and since this row did not delete the row then the |
| * row must have been deleted by a transaction which has committed, so |
| * it is safe to purge the row. It then purges the row from the page. |
| * <p> |
| * Note that this routine may remove all rows from the page, it will not |
| * attempt a merge in this situation. This is because this routine is |
| * called from split which is attempting an insert on the given page, so |
| * it would be a waste to merge the page only to split it again to allow |
| * the insert of the row causing the split. |
| * |
| * @return true if at least one row was purged. If true, then the routine |
| * will leave the page latched, and the caller will release |
| * the latch by committing or aborting the transaction. The |
| * latch must be held to end transaction to insure space on |
| * the page remains available for a undo of the purge. |
| * |
| * @param open_btree The already open btree to use to get latch on page. |
| * @param pageno The page number of the leaf to attempt the reclaim on. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private boolean reclaim_deleted_rows( |
| OpenBTree open_btree, |
| long pageno) |
| throws StandardException |
| { |
| boolean purged_at_least_one_row = false; |
| ControlRow controlRow = null; |
| |
| try |
| { |
| |
| if ((controlRow = ControlRow.get(open_btree, pageno)) == null) |
| return(false); |
| |
| LeafControlRow leaf = (LeafControlRow) controlRow; |
| |
| BTreeLockingPolicy btree_locking_policy = |
| open_btree.getLockingPolicy(); |
| |
| |
| // The number records that can be reclaimed is: |
| // total recs - control row - recs_not_deleted |
| int num_possible_commit_delete = |
| leaf.page.recordCount() - 1 - leaf.page.nonDeletedRecordCount(); |
| |
| if (num_possible_commit_delete > 0) |
| { |
| |
| Page page = leaf.page; |
| |
| |
| // RowLocation column is in last column of template. |
| FetchDescriptor lock_fetch_desc = |
| RowUtil.getFetchDescriptorConstant( |
| scratch_template.length - 1); |
| |
| // loop backward so that purges which affect the slot table |
| // don't affect the loop (ie. they only move records we |
| // have already looked at). |
| for (int slot_no = page.recordCount() - 1; |
| slot_no > 0; |
| slot_no--) |
| { |
| if (page.isDeletedAtSlot(slot_no)) |
| { |
| // try to get an exclusive lock on the row, if we can |
| // then the row is a committed deleted row and it is |
| // safe to purge it. |
| if (btree_locking_policy.lockScanCommittedDeletedRow( |
| open_btree, leaf, scratch_template, |
| lock_fetch_desc, slot_no)) |
| { |
| // the row is a committed deleted row, purge it. |
| page.purgeAtSlot(slot_no, 1, true); |
| |
| purged_at_least_one_row = true; |
| } |
| } |
| } |
| |
| } |
| } |
| catch (java.lang.ClassCastException cce) |
| { |
| // because we give up the latch on the leaf before entering this |
| // routine, the page might change from a leaf to branch. If that |
| // happens this routine will get a ClassCastException, and we |
| // just give up trying to reclaim space. |
| } |
| finally |
| { |
| if (controlRow != null) |
| { |
| if (purged_at_least_one_row) |
| { |
| // Set a hint in the page that scans positioned on it |
| // need to reposition because rows have disappeared from |
| // the page. If at least one row has been purged, then |
| // do not release the latch. Purge requires latch to |
| // be held until commit, where it will be released after |
| // the commit log record has been logged. |
| controlRow.page.setRepositionNeeded(); |
| } |
| else |
| { |
| // Ok to release latch if no purging has happened. |
| controlRow.release(); |
| } |
| } |
| } |
| |
| return(purged_at_least_one_row); |
| } |
| |
| /** |
| * Start an internal transaction and do the split. |
| * <p> |
| * This routine starts a new transaction, and handles any errors that |
| * may come during the transaction. This transation must not obtain any |
| * locks as they are likely to conflict with the current user transaction. |
| * <p> |
| * If attempt_to_reclaim_deleted_rows is true this routine will |
| * attempt to reclaim space on the leaf page input, by purging |
| * committed deleted rows from the leaf. If it succeeds in purging at |
| * least one row, then it will commit the internal transaction and return |
| * without actually performing a split. |
| * |
| * @param scratch_template A scratch template used to search a page. |
| * @param rowToInsert The row to insert, make sure during split to |
| * make room for this row. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private long |
| start_xact_and_dosplit( |
| boolean attempt_to_reclaim_deleted_rows, |
| long leaf_pageno, |
| DataValueDescriptor[] scratch_template, |
| DataValueDescriptor[] rowToInsert, |
| int flag) |
| throws StandardException |
| { |
| TransactionManager split_xact = null; |
| OpenBTree split_open_btree = null; |
| ControlRow root = null; |
| |
| // Get an internal transaction to be used for the split. |
| split_xact = this.init_open_user_scans.getInternalTransaction(); |
| |
| // open the btree again so that actions on it take place in the |
| // split_xact, don't get any locks in this transaction. |
| |
| if (SanityManager.DEBUG) |
| { |
| if (((getOpenMode() & ContainerHandle.MODE_FORUPDATE) != |
| ContainerHandle.MODE_FORUPDATE)) |
| { |
| SanityManager.THROWASSERT( |
| "Container not opened with update should not cause split"); |
| } |
| } |
| |
| |
| boolean do_split = true; |
| if (attempt_to_reclaim_deleted_rows) |
| { |
| // Get lock on base table. |
| |
| ConglomerateController base_cc = null; |
| |
| try |
| { |
| base_cc = |
| this.getConglomerate().lockTable( |
| split_xact, |
| (ContainerHandle.MODE_FORUPDATE | |
| ContainerHandle.MODE_LOCK_NOWAIT), |
| TransactionController.MODE_RECORD, |
| TransactionController.ISOLATION_REPEATABLE_READ); |
| } |
| catch (StandardException se) |
| { |
| // any error just don't try to reclaim deleted rows. The |
| // expected error is that we can't get the lock, which the |
| // current interface throws as a containerNotFound exception. |
| } |
| |
| if (base_cc != null) |
| { |
| // we got IX lock on the base table, so can try reclaim space. |
| |
| |
| // We can only reclaim space by opening the btree in row lock |
| // mode. Table level lock row recovery is hard as we can't |
| // determine if the deleted rows we encounter have been |
| // deleted by our parent caller and have been committed or |
| // not. We will have to get those rows offline. |
| split_open_btree = new OpenBTree(); |
| split_open_btree.init( |
| this.init_open_user_scans, |
| split_xact, |
| null, // open the container. |
| split_xact.getRawStoreXact(), |
| false, |
| (ContainerHandle.MODE_FORUPDATE | |
| ContainerHandle.MODE_LOCK_NOWAIT), |
| TransactionManager.MODE_RECORD, |
| this.getConglomerate().getBtreeLockingPolicy( |
| split_xact.getRawStoreXact(), |
| TransactionController.MODE_RECORD, |
| LockingPolicy.MODE_RECORD, |
| TransactionController.ISOLATION_REPEATABLE_READ, |
| (ConglomerateController) base_cc, |
| split_open_btree), |
| this.getConglomerate(), |
| (LogicalUndo) null, |
| (DynamicCompiledOpenConglomInfo) null); |
| |
| // don't split if we reclaim any rows. |
| do_split = !reclaim_deleted_rows(split_open_btree, leaf_pageno); |
| |
| // on return if !do_split then the latch on leaf_pageno is held |
| // and will be released by the committing or aborting the |
| // transaction. If a purge has been done, no other action on |
| // the page should be attempted (ie. a split) before committing |
| // the purges. |
| |
| split_open_btree.close(); |
| } |
| } |
| |
| long new_leaf_pageno = leaf_pageno; |
| if (do_split) |
| { |
| // no space was reclaimed from deleted rows, so do split to allow |
| // space for a subsequent insert. |
| |
| split_open_btree = new OpenBTree(); |
| split_open_btree.init( |
| this.init_open_user_scans, |
| split_xact, |
| null, // open the container. |
| split_xact.getRawStoreXact(), |
| false, |
| getOpenMode(), // use same mode this controller |
| // was opened with |
| TransactionManager.MODE_NONE, |
| this.getConglomerate().getBtreeLockingPolicy( |
| split_xact.getRawStoreXact(), |
| this.init_lock_level, |
| LockingPolicy.MODE_RECORD, |
| TransactionController.ISOLATION_REPEATABLE_READ, |
| (ConglomerateController) null, // no base row locks during split |
| split_open_btree), |
| this.getConglomerate(), |
| (LogicalUndo) null, |
| (DynamicCompiledOpenConglomInfo) null); |
| |
| |
| // Get the root page back, and perform a split following the |
| // to-be-inserted key. The split releases the root page latch. |
| root = ControlRow.get(split_open_btree, BTree.ROOTPAGEID); |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(root.page.isLatched()); |
| |
| new_leaf_pageno = |
| root.splitFor( |
| split_open_btree, scratch_template, |
| null, rowToInsert, flag); |
| |
| split_open_btree.close(); |
| } |
| |
| split_xact.commit(); |
| |
| split_xact.destroy(); |
| |
| return(new_leaf_pageno); |
| } |
| |
| /** |
| * Compares the oldrow with the one at 'slot' or the one left to it. |
| * If the slot is first slot it will move to the left sibiling of |
| * the 'leaf' and will compare with the record from the last slot. |
| * @param slot slot number to start with |
| * @param leaf LeafControlRow of the current page |
| * @param rows DataValueDescriptot array to fill it with fetched values |
| * @return 0 if no duplicate |
| * 1 if duplicate |
| * 2 if rescan required |
| * @throws StandardException |
| */ |
| private int comparePreviousRecord (int slot, |
| LeafControlRow leaf, |
| DataValueDescriptor [] rows, |
| DataValueDescriptor [] oldRows) |
| throws StandardException { |
| RecordHandle rh = null; |
| boolean newLeaf = false; |
| LeafControlRow originalLeaf = leaf; |
| while (leaf != null) { |
| if (slot == 0) { |
| LeafControlRow oldLeaf = leaf; |
| try { |
| //slot is pointing before the first slot |
| //get left sibiling |
| leaf = (LeafControlRow) leaf.getLeftSibling(this); |
| if (newLeaf) { |
| oldLeaf.release(); |
| } |
| newLeaf = true; |
| //no left sibiling |
| if (leaf == null) |
| return NO_MATCH; |
| //set the slot to last slot number |
| slot = leaf.page.recordCount() - 1; |
| // DERBY-4027: We have moved to the previous page and need |
| // to recheck that the slot number is valid (it won't be |
| // if the page we moved to is empty). Restart from the top |
| // of the loop body to get the slot number rechecked. |
| continue; |
| } catch (WaitError we) { |
| // DERBY-4097: Couldn't latch the left sibling without |
| // waiting. Release all latches and rescan from top of |
| // B-tree to prevent deadlock. |
| if (newLeaf) { |
| oldLeaf.release(); |
| } |
| originalLeaf.release(); |
| return RESCAN_REQUIRED; |
| } |
| } |
| rh = leaf.page.fetchFromSlot(null, slot, rows, null, true); |
| if (rh != null) { |
| int ret = compareRowsForInsert(rows, oldRows, leaf, slot); |
| |
| // If we found a deleted row, we don't know whether there |
| // is a duplicate, so we need to continue the search. |
| final boolean continueSearch = |
| (ret == MATCH_FOUND && leaf.page.isDeletedAtSlot(slot)); |
| |
| if (!continueSearch) { |
| if (newLeaf) { |
| // Since we have moved away from the original leaf, |
| // we need some logic to make sure we don't hold |
| // latches that we're not supposed to hold. |
| if (ret == RESCAN_REQUIRED) { |
| // When a rescan is required, we must release the |
| // original leaf, since the callers expect all |
| // latches to have been released (and so they |
| // should have been, so this is probably a bug - |
| // see DERBY-4080). |
| originalLeaf.release(); |
| } |
| if (ret != RESCAN_REQUIRED) { |
| // Since a rescan is not required, we still hold |
| // the latch on the non-original leaf. No other |
| // leaves than the original one should be latched |
| // when we return, so release the current leaf. |
| leaf.release(); |
| } |
| } |
| return ret; |
| } |
| } |
| slot--; |
| } |
| return NO_MATCH; |
| } |
| |
| /** |
| * Compares the new record with the one at slot or the one |
| * right to it. If the slot is last slot in the page it will move to |
| * the right to sibling of the leaf and will compare with the record |
| * from the last slot. |
| * @param slot slot number to start with |
| * @param leaf LeafControlRow of the current page |
| * @param rows DataValueDescriptot array to fill it with fetched values |
| * @return 0 if no duplicate |
| * 1 if duplicate |
| * 2 if rescan required |
| * @throws StandardException |
| */ |
| private int compareNextRecord (int slot, |
| LeafControlRow leaf, |
| DataValueDescriptor [] rows, |
| DataValueDescriptor [] oldRows) |
| throws StandardException { |
| RecordHandle rh = null; |
| boolean newLeaf = false; |
| LeafControlRow originalLeaf = leaf; |
| while (leaf != null) { |
| if (slot >= leaf.page.recordCount()) { |
| //slot is pointing to last slot |
| //get next sibling |
| LeafControlRow oldLeaf = leaf; |
| leaf = (LeafControlRow) leaf.getRightSibling(this); |
| if (newLeaf) { |
| oldLeaf.release(); |
| } |
| newLeaf = true; |
| //this was right most leaf |
| //no record at the right |
| if (leaf == null) |
| return NO_MATCH; |
| //point slot to the first record of new leaf |
| slot = 1; |
| // DERBY-4027: We have moved to the next page and need |
| // to recheck that the slot number is valid (it won't be |
| // if the page we moved to is empty). Restart from the top |
| // of the loop body to get the slot number rechecked. |
| continue; |
| } |
| rh = leaf.page.fetchFromSlot(null, slot, rows, null, true); |
| if (rh != null) { |
| int ret = compareRowsForInsert(rows, oldRows, leaf, slot); |
| |
| // If we found a deleted row, we don't know whether there |
| // is a duplicate, so we need to continue the search. |
| final boolean continueSearch = |
| (ret == MATCH_FOUND && leaf.page.isDeletedAtSlot(slot)); |
| |
| if (!continueSearch) { |
| if (newLeaf) { |
| // Since we have moved away from the original leaf, |
| // we need some logic to make sure we don't hold |
| // latches that we're not supposed to hold. |
| if (ret == RESCAN_REQUIRED) { |
| // When a rescan is required, we must release the |
| // original leaf, since the callers expect all |
| // latches to have been released (and so they |
| // should have been, so this is probably a bug - |
| // see DERBY-4080). |
| originalLeaf.release(); |
| } |
| if (ret != RESCAN_REQUIRED) { |
| // Since a rescan is not required, we still hold |
| // the latch on the non-original leaf. No other |
| // leaves than the original one should be latched |
| // when we return, so release the current leaf. |
| leaf.release(); |
| } |
| } |
| return ret; |
| } |
| } |
| slot++; |
| } |
| return NO_MATCH; |
| } |
| |
| /** |
| * Compares two rows for insert. If the two rows are not equal, |
| * {@link #NO_MATCH} is returned. Otherwise, it tries to get a lock on |
| * the row in the tree. If the lock is obtained without waiting, |
| * {@link #MATCH_FOUND} is returned (even if the row has been deleted). |
| * Otherwise, {@link #RESCAN_REQUIRED} is returned to indicate that the |
| * latches have been released and the B-tree must be rescanned. |
| * |
| * If {@code MATCH_FOUND} is returned, the caller should check whether |
| * the row has been deleted. If so, it may have to move to check the |
| * adjacent rows to be sure that there is no non-deleted duplicate row. |
| * |
| * If {@code MATCH_FOUND} or {@code RESCAN_REQUIRED} is returned, the |
| * transaction will hold an update lock on the specified record when |
| * the method returns. |
| * |
| * <b>Note!</b> This method should only be called when the index is almost |
| * unique (that is, a non-unique index backing a unique constraint). |
| * |
| * @param originalRow row from the tree |
| * @param newRow row to be inserted |
| * @param leaf leaf where originalRow resides |
| * @param slot slot where originalRow |
| * @return {@code NO_MATCH} if no duplicate is found, |
| * {@code MATCH_FOUND} if a duplicate is found, or |
| * {@code RESCAN_REQUIRED} if the B-tree must be rescanned |
| */ |
| private int compareRowsForInsert (DataValueDescriptor [] originalRow, |
| DataValueDescriptor [] newRow, |
| LeafControlRow leaf, int slot) |
| throws StandardException { |
| for (int i = 0; i < originalRow.length - 1; i++) { |
| if (!originalRow [i].equals(newRow [i])) |
| return NO_MATCH; |
| } |
| //It might be a deleted record try getting a lock on it |
| DataValueDescriptor[] template = runtime_mem.get_template(getRawTran()); |
| FetchDescriptor lock_fetch_desc = RowUtil.getFetchDescriptorConstant( |
| template.length - 1); |
| RowLocation lock_row_loc = |
| (RowLocation) scratch_template[scratch_template.length - 1]; |
| boolean latch_released = !getLockingPolicy().lockNonScanRowOnPage( |
| leaf, slot, lock_fetch_desc, template, |
| lock_row_loc, ConglomerateController.LOCK_UPD); |
| //if latch was released some other transaction was operating on this |
| //record and might have changed the tree by now |
| if (latch_released) |
| return RESCAN_REQUIRED; |
| |
| return MATCH_FOUND; |
| } |
| |
| /** |
| * Compares immidiate left and right records to check for duplicates. |
| * This methods compares new record (being inserted) with the record |
| * in immidate left and right postion to see if its duplicate (only for |
| * almost unique index and for non null keys) |
| * @param rowToInsert row being inserted |
| * @param insert_slot slot where rowToInsert is being inserted |
| * @param targetleaf page where rowToInsert |
| * @return 0 if no duplicate |
| * 1 if duplicate |
| * 2 if rescan required |
| * @throws StandardException |
| */ |
| private int compareLeftAndRightSiblings ( |
| DataValueDescriptor[] rowToInsert, |
| int insert_slot, |
| LeafControlRow targetleaf) throws StandardException { |
| //proceed only if almost unique index |
| if (this.getConglomerate().isUniqueWithDuplicateNulls()) { |
| int keyParts = rowToInsert.length - 1; |
| boolean hasnull = false; |
| for (int i = 0; i < keyParts; i++) { |
| //keys with null in it are unique |
| //no need to compare |
| if (rowToInsert [i].isNull()) { |
| return NO_MATCH; |
| } |
| } |
| if (!hasnull) { |
| DataValueDescriptor index [] = |
| runtime_mem.get_template(getRawTran()); |
| int ret = comparePreviousRecord(insert_slot - 1, |
| targetleaf, index, rowToInsert); |
| if (ret > 0) { |
| return ret; |
| } |
| return compareNextRecord(insert_slot, targetleaf, index, rowToInsert); |
| } |
| } |
| return NO_MATCH; |
| } |
| |
| /** |
| Insert a row into the conglomerate. |
| |
| @param rowToInsert The row to insert into the conglomerate. The stored |
| representations of the row's columns are copied into a new row |
| somewhere in the conglomerate. |
| |
| @return Returns 0 if insert succeeded. Returns |
| ConglomerateController.ROWISDUPLICATE if conglomerate supports uniqueness |
| checks and has been created to disallow duplicates, and the row inserted |
| had key columns which were duplicate of a row already in the table. Other |
| insert failures will raise StandardException's. |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| private int doIns(DataValueDescriptor[] rowToInsert) |
| throws StandardException |
| { |
| LeafControlRow targetleaf = null; |
| LeafControlRow save_targetleaf = null; |
| int insert_slot = 0; |
| int result_slot = 0; |
| int ret_val = 0; |
| boolean reclaim_deleted_rows_attempted = false; |
| |
| if (scratch_template == null) |
| { |
| scratch_template = runtime_mem.get_template(getRawTran()); |
| } |
| |
| if (SanityManager.DEBUG) |
| this.isIndexableRowConsistent(rowToInsert); |
| |
| // Create the objects needed for the insert. |
| // RESOLVE (mikem) - should we cache this in the controller? |
| SearchParameters sp = |
| new SearchParameters( |
| rowToInsert, |
| SearchParameters.POSITION_LEFT_OF_PARTIAL_KEY_MATCH, |
| scratch_template, this, false); |
| |
| // RowLocation column is in last column of template. |
| FetchDescriptor lock_fetch_desc = |
| RowUtil.getFetchDescriptorConstant( |
| scratch_template.length - 1); |
| RowLocation lock_row_loc = |
| (RowLocation) scratch_template[scratch_template.length - 1]; |
| |
| // Row locking - lock the row being inserted. |
| |
| if (get_insert_row_lock) |
| { |
| // I don't hold any latch yet so I can wait on this lock, so I |
| // don't care about return value from this call. This |
| // lock can only wait if the base table row was inserted in a |
| // separate transaction which never happens in sql tables, but |
| // does happen in the sparse indexes that synchronization builds. |
| |
| this.getLockingPolicy().lockNonScanRow( |
| this.getConglomerate(), |
| (LeafControlRow) null, |
| (LeafControlRow) null, |
| rowToInsert, |
| (ConglomerateController.LOCK_INS | |
| ConglomerateController.LOCK_UPD)); |
| } |
| |
| while (true) |
| { |
| // Search the location at which the new row should be inserted. |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(this.container != null); |
| |
| targetleaf = (LeafControlRow) |
| ControlRow.get(this, BTree.ROOTPAGEID).search(sp); |
| |
| |
| // Row locking - first lock row previous to row being inserted: |
| // o if (sp.resultExact) then the row must be deleted and |
| // we will be replacing it with the new row, lock |
| // the row before the slot as the previous key. |
| // o else |
| // we will be inserting after the current slot so |
| // lock the current slot as the previous key. |
| // |
| int slot_after_previous = |
| (sp.resultExact ? sp.resultSlot : sp.resultSlot + 1); |
| |
| boolean latch_released = false; |
| |
| latch_released = |
| !this.getLockingPolicy().lockNonScanPreviousRow( |
| targetleaf, |
| slot_after_previous, |
| lock_fetch_desc, |
| scratch_template, |
| lock_row_loc, |
| this, |
| (ConglomerateController.LOCK_INS_PREVKEY | |
| ConglomerateController.LOCK_UPD), |
| TransactionManager.LOCK_INSTANT_DURATION); |
| |
| // special test to see if latch release code works |
| if (SanityManager.DEBUG) |
| { |
| latch_released = |
| test_errors( |
| this, |
| "BTreeController_doIns", null, |
| this.getLockingPolicy(), |
| targetleaf, latch_released); |
| } |
| |
| if (latch_released) |
| { |
| // Had to release latch in order to get the lock, probably |
| // because of a forward scanner, research tree, and try again. |
| targetleaf = null; |
| continue; |
| } |
| |
| // If the row is there already, simply undelete it. |
| // The rationale for this is, since the index does |
| // not support duplicates, the only way we could |
| // find a duplicate is if we found a deleted row. |
| // If we could lock it, then no other transaction |
| // is deleting it; either this transaction deleted |
| // it earlier, or it's simply a row that the space |
| // reclaimer hasn't reclaimed yet. |
| // Since inserts are done directly (i.e., not to a |
| // location provided by a scan, we will see the |
| // deleted row). |
| if (sp.resultExact) |
| { |
| result_slot = insert_slot = sp.resultSlot; |
| |
| if (this.getConglomerate().nKeyFields != |
| this.getConglomerate().nUniqueColumns) |
| { |
| // The key fields match, but not the row location. We |
| // must wait on the lock on the other row location before |
| // preceding, so as to serialize behind any work being done |
| // to the row as part of another transaction. |
| |
| latch_released = |
| !this.getLockingPolicy().lockNonScanRowOnPage( |
| targetleaf, insert_slot, |
| lock_fetch_desc, scratch_template, lock_row_loc, |
| ConglomerateController.LOCK_UPD); |
| |
| if (latch_released) |
| { |
| // Had to release latch in order to get the lock, |
| // probably to wait for deleting xact to commit or |
| // abort. Research tree, and try again. |
| targetleaf = null; |
| continue; |
| } |
| } |
| |
| // The row better be deleted, or something is very wrong. |
| |
| if (!(targetleaf.page.isDeletedAtSlot(insert_slot))) |
| { |
| // attempt to insert a duplicate into the index. |
| ret_val = ConglomerateController.ROWISDUPLICATE; |
| break; |
| } |
| else |
| { |
| if (this.getConglomerate().nKeyFields == |
| this.getConglomerate().nUniqueColumns) |
| { |
| // The row that we found deleted is exactly the new row. |
| targetleaf.page.deleteAtSlot( |
| insert_slot, false, this.btree_undo); |
| |
| break; |
| } |
| else if (this.getConglomerate().nUniqueColumns == |
| (this.getConglomerate().nKeyFields - 1)) |
| { |
| // The row that we found deleted has matching keys |
| // which form the unique key fields, |
| // but the nonkey fields may differ (for now the |
| // heap rowlocation is the only nonkey field |
| // allowed). |
| |
| // RESOLVE BT39 (mikem) - when/if heap row location |
| // is not fixed we must handle update failing for |
| // out of space and split if it does. For now |
| // if the update fails because of lack of space |
| // an exception is thrown and the statement is |
| // backed out. Should not happen very often. |
| targetleaf.page.deleteAtSlot( |
| insert_slot, false, this.btree_undo); |
| |
| boolean update_succeeded = true; |
| try |
| { |
| if (runtime_mem.hasCollatedTypes()) |
| { |
| // See DERBY-5367. |
| // There are types in the BTree with a |
| // collation different than UCS BASIC, we |
| // update all fields to make sure they hold |
| // the correct values. |
| // NOTE: We could optimize here by only |
| // updating the fields that actually hold |
| // collated types. |
| int rowsToUpdate = getConglomerate().nKeyFields; |
| for (int i=0; i < rowsToUpdate; i++) { |
| targetleaf.page.updateFieldAtSlot( |
| insert_slot, i, |
| (DataValueDescriptor) RowUtil.getColumn( |
| rowToInsert, |
| (FormatableBitSet) null, i), |
| this.btree_undo); |
| } |
| } |
| else |
| { |
| // There are no collated types in the BTree, |
| // which means that the values currently |
| // stored in the undeleted row are correct. |
| // We simply update the row location to point |
| // to the correct row in the heap. |
| int rowloc_index = |
| this.getConglomerate().nKeyFields - 1; |
| targetleaf.page.updateFieldAtSlot( |
| insert_slot, rowloc_index, |
| (DataValueDescriptor) RowUtil.getColumn( |
| rowToInsert, |
| (FormatableBitSet) null, rowloc_index), |
| this.btree_undo); |
| } |
| } |
| catch (StandardException se) |
| { |
| // check if the exception is for out of space |
| if (!se.getMessageId().equals(SQLState.DATA_NO_SPACE_FOR_RECORD)) |
| { |
| throw se; |
| } |
| |
| // The statement exception is |
| // because the update failed for out of |
| // space (ie. the field got longer and there |
| // is no room on the page for the expanded |
| // field). Address this error by falling |
| // through the code and doing a split. |
| update_succeeded = false; // update failed. |
| targetleaf.page.deleteAtSlot( |
| insert_slot, true, this.btree_undo); |
| } |
| |
| if (update_succeeded) |
| break; |
| } |
| else |
| { |
| // Can only happen with non key fields in the btree. |
| throw( |
| StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE)); |
| } |
| } |
| } |
| else if (targetleaf.page.recordCount() - 1 < BTree.maxRowsPerPage) |
| { |
| // The row wasn't there, so try to insert it |
| // on the page returned by the search. |
| insert_slot = sp.resultSlot + 1; |
| result_slot = insert_slot + 1; |
| if (getConglomerate().isUniqueWithDuplicateNulls()) |
| { |
| int ret = compareLeftAndRightSiblings(rowToInsert, |
| insert_slot, targetleaf); |
| if (ret == MATCH_FOUND) |
| { |
| ret_val = ConglomerateController.ROWISDUPLICATE; |
| break; |
| } |
| if (ret == RESCAN_REQUIRED) |
| continue; |
| } |
| // By default maxRowsPerPage is set to MAXINT, some tests |
| // set it small to cause splitting to happen quicker with |
| // less data. |
| |
| if (targetleaf.page.insertAtSlot( |
| insert_slot, |
| rowToInsert, (FormatableBitSet) null, |
| this.btree_undo, |
| Page.INSERT_DEFAULT, |
| AccessFactoryGlobals.BTREE_OVERFLOW_THRESHOLD) != null) |
| { |
| // Insert succeeded, so we're done. |
| |
| break; |
| } |
| |
| // RESOLVE (mikem) - another long row issue. |
| // For now if a row does not fit on a page and there |
| // is only the control row on the page and at most one |
| // other row on the page, throw an exception |
| |
| if (targetleaf.page.recordCount() <= 2) |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_NO_SPACE_FOR_KEY); |
| } |
| |
| // start splitting ... |
| } |
| if (getConglomerate().isUniqueWithDuplicateNulls()) |
| { |
| int ret = compareLeftAndRightSiblings(rowToInsert, |
| insert_slot, targetleaf); |
| if (ret == MATCH_FOUND) |
| { |
| ret_val = ConglomerateController.ROWISDUPLICATE; |
| break; |
| } |
| if (ret == RESCAN_REQUIRED) |
| continue; |
| } |
| |
| // Create some space by splitting pages. |
| |
| // determine where in page/table row causing split would go |
| int flag = 0; |
| if (insert_slot == 1) |
| { |
| flag |= ControlRow.SPLIT_FLAG_FIRST_ON_PAGE; |
| if (targetleaf.isLeftmostLeaf()) |
| flag |= ControlRow.SPLIT_FLAG_FIRST_IN_TABLE; |
| } |
| else if (insert_slot == targetleaf.page.recordCount()) |
| { |
| flag |= ControlRow.SPLIT_FLAG_LAST_ON_PAGE; |
| if (targetleaf.isRightmostLeaf()) |
| flag |= ControlRow.SPLIT_FLAG_LAST_IN_TABLE; |
| } |
| |
| long targetleaf_pageno = targetleaf.page.getPageNumber(); |
| |
| if ((targetleaf.page.recordCount() - |
| targetleaf.page.nonDeletedRecordCount()) <= 0) |
| { |
| // Don't do reclaim work if there are no deleted records. |
| reclaim_deleted_rows_attempted = true; |
| } |
| |
| BranchRow branchrow = |
| BranchRow.createBranchRowFromOldLeafRow( |
| rowToInsert, targetleaf_pageno); |
| |
| // Release the target page because (a) it may change as a |
| // result of the split, (b) the latch ordering requires us |
| // to acquire latches from top to bottom, and (c) this |
| // loop should be done in a system transaction. |
| targetleaf.release(); |
| targetleaf = null; |
| |
| start_xact_and_dosplit( |
| !reclaim_deleted_rows_attempted, targetleaf_pageno, |
| scratch_template, branchrow.getRow(), flag); |
| |
| // only attempt to reclaim deleted rows once, otherwise the |
| // split loop could loop forever, trying to reclaim a deleted |
| // row that was not committed. |
| reclaim_deleted_rows_attempted = true; |
| |
| // RESOLVE (mikem) possible optimization could be to save |
| // split location and look there first, if this has |
| // already caused a split. Or even return a latched page |
| // from splitFor(). For now just execute the loop again |
| // searching the tree for somewhere to put the row. |
| } |
| |
| // set in-memory hint of where last row on page was inserted. |
| targetleaf.last_search_result = result_slot; |
| |
| // Check that page just updated is consistent. |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("enableBtreeConsistencyCheck")) |
| { |
| targetleaf.checkConsistency(this, null, true); |
| } |
| } |
| |
| // Done with the target page. |
| targetleaf.release(); |
| targetleaf = null; |
| |
| // return the status about insert - 0 is ok, or duplicate status. |
| return(ret_val); |
| } |
| |
| /** |
| * Just insert the row on the current page/slot if it fits. |
| * <p> |
| * @exception StandardException Standard exception policy. |
| **/ |
| private boolean do_load_insert( |
| DataValueDescriptor[] rowToInsert, |
| LeafControlRow leaf, |
| int insert_slot) |
| throws StandardException |
| { |
| LeafControlRow old_leaf = null; |
| boolean row_inserted = false; |
| int num_rows_on_page = leaf.page.recordCount() - 1; |
| |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(insert_slot == leaf.page.recordCount()); |
| SanityManager.ASSERT( |
| leaf.getrightSiblingPageNumber() == |
| ContainerHandle.INVALID_PAGE_NUMBER); |
| this.isIndexableRowConsistent(rowToInsert); |
| } |
| |
| if (num_rows_on_page < BTree.maxRowsPerPage) |
| { |
| // By default maxRowsPerPage is set to MAXINT, some tests |
| // set it small to cause splitting to happen quicker with |
| // less data. |
| |
| if (SanityManager.DEBUG) |
| { |
| // Caller should have sorted and done duplicate checking. |
| |
| if (insert_slot > 1) |
| { |
| // verify that the row inserted is >= than previous row. |
| int compare_result = |
| ControlRow.compareIndexRowFromPageToKey( |
| leaf, |
| insert_slot - 1, |
| scratch_template, |
| rowToInsert, |
| this.getConglomerate().nUniqueColumns, |
| 0, |
| this.getConglomerate().ascDescInfo); |
| |
| if (compare_result >= 0) |
| { |
| // Rows must be presented in order, so the row we are |
| // inserting must always be greater than the previous |
| // row on the page. |
| SanityManager.THROWASSERT("result = " + compare_result); |
| } |
| } |
| } |
| |
| |
| if (leaf.page.insertAtSlot( |
| insert_slot, |
| rowToInsert, |
| (FormatableBitSet) null, |
| this.btree_undo, |
| Page.INSERT_DEFAULT, |
| AccessFactoryGlobals.BTREE_OVERFLOW_THRESHOLD) != null) |
| { |
| // Insert succeeded, so we're done. |
| row_inserted = true; |
| } |
| else |
| { |
| // RESOLVE (mikem) - another long row issue. |
| // For now if a row does not fit on a page and there |
| // is only the control row on the page and at most one |
| // other row on the page, throw an exception |
| |
| if (leaf.page.recordCount() <= 2) |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_NO_SPACE_FOR_KEY); |
| } |
| } |
| } |
| |
| // Check that page just updated is consistent. |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON("enableBtreeConsistencyCheck")) |
| { |
| leaf.checkConsistency(this, null, true); |
| } |
| } |
| |
| return(row_inserted); |
| } |
| |
| /** |
| * Create room to insert a row to the right of the largest key in table. |
| * <p> |
| * Perform a split pass on the tree which will move the largest key in |
| * leaf right to a new leaf, splitting parent branch pages as necessary. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| private LeafControlRow do_load_split( |
| DataValueDescriptor[] rowToInsert, |
| LeafControlRow leaf) |
| throws StandardException |
| { |
| LeafControlRow new_leaf = null; |
| |
| BranchRow branchrow = |
| BranchRow.createBranchRowFromOldLeafRow( |
| rowToInsert, leaf.page.getPageNumber()); |
| |
| // Release the target page because (a) it may change as a |
| // result of the split, (b) the latch ordering requires us |
| // to acquire latches from top to bottom, and (c) this |
| // loop should be done in a system transaction. |
| long old_leafpage = leaf.page.getPageNumber(); |
| |
| leaf.release(); |
| leaf = null; |
| |
| long new_leaf_pageno = |
| start_xact_and_dosplit( |
| false /* don't try to reclaim deleted rows */, |
| old_leafpage, |
| scratch_template, |
| branchrow.getRow(), |
| (ControlRow.SPLIT_FLAG_LAST_ON_PAGE | |
| ControlRow.SPLIT_FLAG_LAST_IN_TABLE)); |
| |
| new_leaf = (LeafControlRow) ControlRow.get(this, new_leaf_pageno); |
| |
| // The leaf must be the rightmost leaf in the table, the first time |
| // the root grows from leaf to branch it will be a leaf with many |
| // rows which will probably have to be split soon, after that it will |
| // be a leaf with only one row. The current algorithm requires that |
| // there be at least one row for duplicate checking (the duplicate |
| // checking code does not handle going left to the previous leaf) - |
| // this is the way the split at rightmost leaf row works currently. |
| if (SanityManager.DEBUG) |
| { |
| if (new_leaf.getrightSiblingPageNumber() != |
| ContainerHandle.INVALID_PAGE_NUMBER) |
| { |
| SanityManager.THROWASSERT( |
| "new_leaf.getrightSiblingPageNumber() = " + |
| new_leaf.getrightSiblingPageNumber()); |
| } |
| if (new_leaf.page.recordCount() <= 1) |
| { |
| SanityManager.THROWASSERT( |
| "new_leaf.page.recordCount() = " + |
| new_leaf.page.recordCount()); |
| } |
| } |
| |
| return(new_leaf); |
| } |
| |
| |
| |
| /* |
| ** public Methods of BTreeController |
| */ |
| |
| /** |
| Initialize the controller for use. |
| <p> |
| Any changes to this method will probably have to be reflected in close as |
| well. |
| <p> |
| Currently delegates to OpenBTree. If the btree controller ends up not |
| having any state of its own, we can remove this method (the VM will |
| dispatch to OpenBTree), gaining some small efficiency. For now, this |
| method remains for clarity. |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public void init( |
| TransactionManager xact_manager, |
| boolean hold, |
| ContainerHandle container, |
| Transaction rawtran, |
| int open_mode, |
| int lock_level, |
| BTreeLockingPolicy btree_locking_policy, |
| BTree conglomerate, |
| LogicalUndo undo, |
| StaticCompiledOpenConglomInfo static_info, |
| DynamicCompiledOpenConglomInfo dynamic_info) |
| throws StandardException |
| { |
| get_insert_row_lock = |
| ((open_mode & |
| TransactionController.OPENMODE_BASEROW_INSERT_LOCKED) == 0); |
| |
| super.init( |
| xact_manager, xact_manager, |
| container, rawtran, hold, open_mode, |
| lock_level, btree_locking_policy, |
| conglomerate, undo, dynamic_info); |
| } |
| |
| /* |
| ** Methods of ConglomerateController |
| */ |
| |
| /** |
| Close the conglomerate controller. |
| <p> |
| Any changes to this method will probably have to be reflected in close as |
| well. |
| <p> |
| Currently delegates to OpenBTree. If the btree controller ends up not |
| having any state of its own, we can remove this method (the VM will |
| dispatch to OpenBTree), gaining some small efficiency. For now, this |
| method remains for clarity. |
| |
| @see ConglomerateController#close |
| **/ |
| public void close() |
| throws StandardException |
| { |
| super.close(); |
| |
| // If we are closed due to catching an error in the middle of init, |
| // xact_manager may not be set yet. |
| if (getXactMgr() != null) |
| getXactMgr().closeMe(this); |
| } |
| |
| /** |
| * Close conglomerate controller as part of terminating a transaction. |
| * <p> |
| * Use this call to close the conglomerate controller resources as part of |
| * committing or aborting a transaction. The normal close() routine may |
| * do some cleanup that is either unnecessary, or not correct due to the |
| * unknown condition of the controller following a transaction ending error. |
| * Use this call when closing all controllers as part of an abort of a |
| * transaction. |
| * <p> |
| * This call is meant to only be used internally by the Storage system, |
| * clients of the storage system should use the simple close() interface. |
| * <p> |
| * RESOLVE (mikem) - move this call to ConglomerateManager so it is |
| * obvious that non-access clients should not call this. |
| * |
| * @param closeHeldScan If true, means to close controller even if |
| * it has been opened to be kept opened |
| * across commit. This is |
| * used to close these controllers on abort. |
| * |
| * @return boolean indicating that the close has resulted in a real close |
| * of the controller. A held scan will return false if |
| * called by closeForEndTransaction(false), otherwise it |
| * will return true. A non-held scan will always return |
| * true. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public boolean closeForEndTransaction(boolean closeHeldScan) |
| throws StandardException |
| { |
| super.close(); |
| |
| if ((!getHold()) || closeHeldScan) |
| { |
| // If we are closed due to catching an error in the middle of init, |
| // xact_manager may not be set yet. |
| if (getXactMgr() != null) |
| getXactMgr().closeMe(this); |
| |
| return(true); |
| } |
| else |
| { |
| return(false); |
| } |
| } |
| |
| /** |
| Insert a row into the conglomerate. |
| @see ConglomerateController#insert |
| |
| @param row The row to insert into the conglomerate. The stored |
| representations of the row's columns are copied into a new row |
| somewhere in the conglomerate. |
| |
| @return Returns 0 if insert succeeded. Returns |
| ConglomerateController.ROWISDUPLICATE if conglomerate supports uniqueness |
| checks and has been created to disallow duplicates, and the row inserted |
| had key columns which were duplicate of a row already in the table. Other |
| insert failures will raise StandardException's. |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public int insert(DataValueDescriptor[] row) |
| throws StandardException |
| { |
| |
| if (isClosed()) |
| { |
| if (getHold()) |
| { |
| reopen(); |
| } |
| else |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_IS_CLOSED, |
| err_containerid); |
| } |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(this.container != null); |
| } |
| |
| return doIns(row); |
| } |
| |
| /** |
| Return whether this is a keyed conglomerate. |
| <p> |
| All b-trees are keyed. |
| @see ConglomerateController#isKeyed |
| **/ |
| public boolean isKeyed() |
| { |
| return(true); |
| } |
| |
| /** |
| * Request the system properties associated with a table. |
| * <p> |
| * Request the value of properties that are associated with a table. The |
| * following properties can be requested: |
| * derby.storage.pageSize |
| * derby.storage.pageReservedSpace |
| * derby.storage.minimumRecordSize |
| * derby.storage.initialPages |
| * <p> |
| * To get the value of a particular property add it to the property list, |
| * and on return the value of the property will be set to it's current |
| * value. For example: |
| * |
| * get_prop(ConglomerateController cc) |
| * { |
| * Properties prop = new Properties(); |
| * prop.put("derby.storage.pageSize", ""); |
| * cc.getTableProperties(prop); |
| * |
| * System.out.println( |
| * "table's page size = " + |
| * prop.getProperty("derby.storage.pageSize"); |
| * } |
| * |
| * @param prop Property list to fill in. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public void getTableProperties(Properties prop) |
| throws StandardException |
| { |
| if (this.container == null) |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_IS_CLOSED, |
| err_containerid); |
| } |
| |
| container.getContainerProperties(prop); |
| |
| return; |
| } |
| |
| /** |
| * Request set of properties associated with a table. |
| * <p> |
| * Returns a property object containing all properties that the store |
| * knows about, which are stored persistently by the store. This set |
| * of properties may vary from implementation to implementation of the |
| * store. |
| * <p> |
| * This call is meant to be used only for internal query of the properties |
| * by jbms, for instance by language during bulk insert so that it can |
| * create a new conglomerate which exactly matches the properties that |
| * the original container was created with. This call should not be used |
| * by the user interface to present properties to users as it may contain |
| * properties that are meant to be internal to jbms. Some properties are |
| * meant only to be specified by jbms code and not by users on the command |
| * line. |
| * <p> |
| * Note that not all properties passed into createConglomerate() are stored |
| * persistently, and that set may vary by store implementation. |
| * |
| * @param prop Property list to add properties to. If null, routine will |
| * create a new Properties object, fill it in and return it. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public Properties getInternalTablePropertySet(Properties prop) |
| throws StandardException |
| { |
| Properties ret_properties = |
| ConglomerateUtil.createRawStorePropertySet(prop); |
| |
| getTableProperties(ret_properties); |
| |
| return(ret_properties); |
| } |
| |
| /** |
| * Load rows from rowSource into the opened btree. |
| * <p> |
| * Efficiently load rows into the already opened btree. The btree must |
| * be table locked, as no row locks will be requested by this routine. |
| * On exit from this routine the conglomerate will be closed (on both |
| * error or success). |
| * <p> |
| * This routine does an almost bottom up build of a btree. It assumes |
| * all rows arrive in sorted order, and inserts them directly into the |
| * next (to the right) spot in the current leaf until there is no space. |
| * Then it calls the generic split code to add the next leaf (RESOLVE - |
| * in the future we could optimize this to split bottom up rather than |
| * top down for create index). |
| * |
| * @exception StandardException Standard exception policy. If conglomerate |
| * supports uniqueness checks and has been |
| * created to disallow duplicates, and one of |
| * the rows being loaded had key columns which |
| * were duplicate of a row already in the |
| * conglomerate, then raise |
| * SQLState.STORE_CONGLOMERATE_DUPLICATE_KEY_EXCEPTION. |
| * |
| * @see org.apache.derby.iapi.store.access.conglomerate.Conglomerate#load |
| **/ |
| public long load( |
| TransactionManager xact_manager, |
| boolean createConglom, |
| RowLocationRetRowSource rowSource) |
| throws StandardException |
| { |
| long num_rows_loaded = 0; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(createConglom, |
| "Cannot load a btree incrementally - it must either be entirely logged, or entirely not logged. Doesn't make sense to log only the allocation when one cannot guarantee to not touch any pre-existing pages"); |
| } |
| |
| if (scratch_template == null) |
| { |
| scratch_template = runtime_mem.get_template(getRawTran()); |
| } |
| |
| LeafControlRow current_leaf = null; |
| |
| try |
| { |
| // Btree must just have been created and empty, so there must |
| // be one root leaf page which is empty except for the control row. |
| current_leaf = |
| (LeafControlRow) ControlRow.get(this, BTree.ROOTPAGEID); |
| int current_insert_slot = 1; |
| |
| if (SanityManager.DEBUG) |
| { |
| // root must be empty except for the control row. |
| SanityManager.ASSERT(current_leaf.page.recordCount() == 1); |
| } |
| |
| // now loop thru the row source and insert into the btree |
| FormatableBitSet validColumns = rowSource.getValidColumns(); |
| |
| // get the next row and its valid columns from the rowSource |
| DataValueDescriptor[] row; |
| while ((row = rowSource.getNextRowFromRowSource()) != null) |
| { |
| num_rows_loaded++; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| validColumns == null, "Does not support partial row"); |
| } |
| |
| while (true) |
| { |
| if (do_load_insert(row, current_leaf, current_insert_slot)) |
| { |
| // row inserted successfully. |
| break; |
| } |
| else |
| { |
| // if insert fails, do a split pass. There is an edge |
| // case where multiple split passes are necessary if |
| // branch splits are necessary, thus the loop. It is |
| // most likely that only a single split pass will be |
| // necessary. |
| current_leaf = do_load_split(row, current_leaf); |
| |
| current_insert_slot = current_leaf.page.recordCount(); |
| } |
| } |
| current_insert_slot++; |
| } |
| |
| current_leaf.release(); |
| current_leaf = null; |
| |
| // Loading done, must flush all pages to disk since it is unlogged. |
| if (!this.getConglomerate().isTemporary()) |
| container.flushContainer(); |
| } |
| finally |
| { |
| this.close(); |
| } |
| |
| return(num_rows_loaded); |
| } |
| |
| /* |
| ** Methods of ConglomerateController which are not supported. |
| */ |
| |
| /** |
| Delete a row from the conglomerate. |
| @see ConglomerateController#delete |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public boolean delete(RowLocation loc) |
| throws StandardException |
| { |
| throw(StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE)); |
| } |
| |
| /** |
| Fetch the row at the given location. |
| @see ConglomerateController#fetch |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public boolean fetch( |
| RowLocation loc, |
| DataValueDescriptor[] row, |
| FormatableBitSet validColumns) |
| throws StandardException |
| { |
| throw(StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE)); |
| } |
| |
| /** |
| Fetch the row at the given location. |
| @see ConglomerateController#fetch |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public boolean fetch( |
| RowLocation loc, |
| DataValueDescriptor[] row, |
| FormatableBitSet validColumns, |
| boolean waitForLock) |
| throws StandardException |
| { |
| throw(StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE)); |
| } |
| |
| /** |
| Insert a row into the conglomerate, and store its location in the |
| provided template row location. |
| |
| Unimplemented by btree. |
| |
| @see ConglomerateController#insertAndFetchLocation |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public void insertAndFetchLocation( |
| DataValueDescriptor[] row, |
| RowLocation templateRowLocation) |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| |
| /** |
| Return a row location object of the correct type to be |
| used in calls to insertAndFetchLocation. |
| |
| @see ConglomerateController#newRowLocationTemplate |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public RowLocation newRowLocationTemplate() |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| |
| /** |
| * Lock the given row location. |
| * <p> |
| * Should only be called by access. |
| * <p> |
| * This call can be made on a ConglomerateController that was opened |
| * for locking only. |
| * <p> |
| * RESOLVE (mikem) - move this call to ConglomerateManager so it is |
| * obvious that non-access clients should not call this. |
| * |
| * @return true if lock was granted, only can be false if wait was false. |
| * |
| * @param loc The "RowLocation" which describes the exact row to lock. |
| * @param wait Should the lock call wait to be granted? |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| public boolean lockRow( |
| RowLocation loc, |
| int lock_operation, |
| boolean wait, |
| int lock_duration) |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| |
| public boolean lockRow( |
| long page_num, |
| int record_id, |
| int lock_operation, |
| boolean wait, |
| int lock_duration) |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| |
| public void unlockRowAfterRead( |
| RowLocation loc, |
| boolean forUpdate, |
| boolean row_qualifies) |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| |
| /** |
| Replace the entire row at the given location. |
| @see ConglomerateController#replace |
| |
| @exception StandardException Standard exception policy. |
| **/ |
| public boolean replace( |
| RowLocation loc, |
| DataValueDescriptor[] row, |
| FormatableBitSet validColumns) |
| throws StandardException |
| { |
| throw StandardException.newException( |
| SQLState.BTREE_UNIMPLEMENTED_FEATURE); |
| } |
| } |