--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.access.btree.BTreeScan\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.access.btree;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.store.access.conglomerate.Conglomerate;\r
+import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo;\r
+import org.apache.derby.iapi.store.access.conglomerate.ScanManager;\r
+import org.apache.derby.iapi.store.access.conglomerate.TransactionManager;\r
+\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.Qualifier;\r
+import org.apache.derby.iapi.store.access.RowUtil;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.ScanInfo;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+\r
+import org.apache.derby.iapi.store.raw.ContainerHandle;\r
+import org.apache.derby.iapi.store.raw.FetchDescriptor;\r
+import org.apache.derby.iapi.store.raw.LockingPolicy;\r
+import org.apache.derby.iapi.store.raw.Page;\r
+import org.apache.derby.iapi.store.raw.RecordHandle;\r
+import org.apache.derby.iapi.store.raw.Transaction;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+import org.apache.derby.impl.store.access.conglomerate.TemplateRow;\r
+\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.store.access.BackingStoreHashtable;\r
+\r
+/**\r
+\r
+ A b-tree scan controller corresponds to an instance of an open b-tree scan.\r
+ <P>\r
+ <B>Concurrency Notes<\B>\r
+ <P>\r
+ The concurrency rules are derived from OpenBTree.\r
+ <P>\r
+ @see OpenBTree\r
+\r
+**/\r
+\r
+public abstract class BTreeScan extends OpenBTree implements ScanManager\r
+{\r
+\r
+ /*\r
+ ** Fields of BTreeScan\r
+ */\r
+\r
+ /**\r
+ * init_startKeyValue, init_qualifier, and init_stopKeyValue all are used \r
+ * to store * references to the values passed in when ScanController.init()\r
+ * is called. It is assumed that these are not altered by the client\r
+ * while the scan is active.\r
+ */\r
+ protected Transaction init_rawtran = null;\r
+ protected boolean init_forUpdate;\r
+ protected FormatableBitSet init_scanColumnList;\r
+ protected DataValueDescriptor[] init_template;\r
+ protected DataValueDescriptor[] init_startKeyValue;\r
+ protected int init_startSearchOperator = 0;\r
+ protected Qualifier init_qualifier[][] = null;\r
+ protected DataValueDescriptor[] init_stopKeyValue;\r
+ protected int init_stopSearchOperator = 0;\r
+ protected boolean init_hold;\r
+\r
+\r
+ /**\r
+ * The fetch descriptor which describes the row to be returned by the scan.\r
+ **/\r
+ protected FetchDescriptor init_fetchDesc;\r
+\r
+\r
+ /**\r
+ * A constant FetchDescriptor which describes the position of the \r
+ * RowLocation field within the btree, currently always the last column). \r
+ * Used by lock/unlock to fetch the RowLocation. \r
+ * Only needs to be allocated once per scan.\r
+ **/\r
+ protected FetchDescriptor init_lock_fetch_desc;\r
+\r
+\r
+ BTreeRowPosition scan_position;\r
+\r
+\r
+ /**\r
+ * Whether the scan should requests UPDATE locks which then will be \r
+ * converted to X locks when the actual operation is performed.\r
+ **/\r
+ protected boolean init_useUpdateLocks = false;\r
+\r
+ /*\r
+ * There are 5 states a scan can be in.\r
+ * SCAN_INIT - A scan has started but no positioning has been done.\r
+ * The scan will be positioned when the first next() call\r
+ * has been made. None of the positioning state variables\r
+ * are valid in this state.\r
+ * SCAN_INPROGRESS -\r
+ * A scan is in this state after the first next() call.\r
+ * On exit from any BTreeScan method, while in this state,\r
+ * the scan "points" at a row which qualifies for the \r
+ * scan. While not maintaining latches on a page the \r
+ * current position of the scan is either kept by record\r
+ * handle or key. To tell which use the following:\r
+ * if (record key == null)\r
+ * record handle has current position\r
+ * else\r
+ * record key has current position\r
+ *\r
+ * SCAN_DONE - Once the end of the table or the stop condition is met\r
+ * then the scan is placed in this state. Only valid \r
+ * ScanController method at this point is close().\r
+ *\r
+ * SCAN_HOLD_INIT -\r
+ * The scan has been opened and held open across a commit,\r
+ * at the last commit the state was SCAN_INIT.\r
+ * The scan has never progressed from the SCAN_INIT state\r
+ * during a transaction. When a next is done the state\r
+ * will either progress to SCAN_INPROGRESS or SCAN_DONE.\r
+ *\r
+ * SCAN_HOLD_INPROGRESS -\r
+ * The scan has been opened and held open across a commit,\r
+ * at the last commit the state was in SCAN_INPROGRESS.\r
+ * The transaction which opened the scan has committed,\r
+ * but the scan was opened with the "hold" option true.\r
+ * At commit the locks were released and the "current"\r
+ * position is remembered. In this state only two calls\r
+ * are valid, either next() or close(). When next() is\r
+ * called the scan is reopened, the underlying container\r
+ * is opened thus associating all new locks with the current\r
+ * transaction, and the scan continues at the "next" row.\r
+ */\r
+ protected static final int SCAN_INIT = 1;\r
+ protected static final int SCAN_INPROGRESS = 2;\r
+ protected static final int SCAN_DONE = 3;\r
+ protected static final int SCAN_HOLD_INIT = 4;\r
+ protected static final int SCAN_HOLD_INPROGRESS = 5;\r
+\r
+ /**\r
+ * Delay positioning the table at the start position until the first\r
+ * next() call. The initial position is done in positionAtStartPosition().\r
+ */\r
+ protected int scan_state = SCAN_INIT;\r
+\r
+ /**\r
+ * Performance counters ...\r
+ */\r
+ protected int stat_numpages_visited = 0;\r
+ protected int stat_numrows_visited = 0;\r
+ protected int stat_numrows_qualified = 0;\r
+ protected int stat_numdeleted_rows_visited = 0;\r
+\r
+ /**\r
+ * What kind of row locks to get during the scan.\r
+ **/\r
+ protected int lock_operation;\r
+\r
+\r
+ /**\r
+ * A 1 element array to turn fetchNext and fetch calls into \r
+ * fetchNextGroup calls.\r
+ **/\r
+ protected DataValueDescriptor[][] fetchNext_one_slot_array = \r
+ new DataValueDescriptor[1][];\r
+\r
+ /* Constructors for This class: */\r
+\r
+ public BTreeScan()\r
+ {\r
+ }\r
+\r
+ /*\r
+ ** Private/Protected methods of This class, sorted alphabetically\r
+ */\r
+\r
+ /**\r
+ * Fetch the next N rows from the table.\r
+ * <p>\r
+ * Utility routine used by both fetchSet() and fetchNextGroup().\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ abstract protected int fetchRows(\r
+ BTreeRowPosition pos,\r
+ DataValueDescriptor[][] row_array,\r
+ RowLocation[] rowloc_array,\r
+ BackingStoreHashtable hash_table,\r
+ long max_rowcnt,\r
+ int[] key_column_numbers)\r
+ throws StandardException;\r
+\r
+\r
+ /**\r
+ * Shared initialization code between init() and reopenScan().\r
+ * <p>\r
+ * Basically save away input parameters describing qualifications for\r
+ * the scan, and do some error checking.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private void initScanParams(\r
+ DataValueDescriptor[] startKeyValue,\r
+ int startSearchOperator,\r
+ Qualifier qualifier[][],\r
+ DataValueDescriptor[] stopKeyValue,\r
+ int stopSearchOperator)\r
+ throws StandardException\r
+ {\r
+ // startKeyValue init.\r
+ this.init_startKeyValue = startKeyValue;\r
+ if (RowUtil.isRowEmpty(this.init_startKeyValue))\r
+ this.init_startKeyValue = null;\r
+\r
+ // startSearchOperator init.\r
+ this.init_startSearchOperator = startSearchOperator;\r
+\r
+ // qualifier init.\r
+ if ((qualifier != null) && (qualifier .length == 0))\r
+ qualifier = null;\r
+ this.init_qualifier = qualifier;\r
+\r
+ // stopKeyValue init.\r
+ this.init_stopKeyValue = stopKeyValue;\r
+ if (RowUtil.isRowEmpty(this.init_stopKeyValue))\r
+ this.init_stopKeyValue = null;\r
+\r
+ // stopSearchOperator init.\r
+ this.init_stopSearchOperator = stopSearchOperator;\r
+\r
+ // reset the "current" position to starting condition.\r
+ // RESOLVE (mmm) - "compile" this.\r
+ scan_position = new BTreeRowPosition();\r
+\r
+ scan_position.init();\r
+\r
+ scan_position.current_lock_template = \r
+ new DataValueDescriptor[this.init_template.length];\r
+\r
+ scan_position.current_lock_template[this.init_template.length - 1] = \r
+ scan_position.current_lock_row_loc = \r
+ (RowLocation) ((RowLocation) \r
+ init_template[init_template.length - 1]).cloneObject(); \r
+\r
+ // Verify that all columns in start key value, stop key value, and\r
+ // qualifiers are present in the list of columns described by the\r
+ // scanColumnList.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (init_scanColumnList != null)\r
+ {\r
+ // verify that all columns specified in qualifiers, start\r
+ // and stop positions are specified in the scanColumnList. \r
+ \r
+ FormatableBitSet required_cols;\r
+\r
+ if (qualifier != null)\r
+ required_cols = RowUtil.getQualifierBitSet(qualifier);\r
+ else\r
+ required_cols = new FormatableBitSet(0);\r
+\r
+ // add in start columns\r
+ if (this.init_startKeyValue != null)\r
+ {\r
+ required_cols.grow(this.init_startKeyValue.length);\r
+ for (int i = 0; i < this.init_startKeyValue.length; i++)\r
+ required_cols.set(i);\r
+ }\r
+\r
+ if (this.init_stopKeyValue != null)\r
+ {\r
+ required_cols.grow(this.init_stopKeyValue.length);\r
+ for (int i = 0; i < this.init_stopKeyValue.length; i++)\r
+ required_cols.set(i);\r
+ }\r
+\r
+ FormatableBitSet required_cols_and_scan_list = \r
+ (FormatableBitSet) required_cols.clone();\r
+\r
+ required_cols_and_scan_list.and(init_scanColumnList);\r
+\r
+ // FormatableBitSet equals requires the two FormatableBitSets to be of same\r
+ // length.\r
+ required_cols.grow(init_scanColumnList.size());\r
+\r
+ if (!required_cols_and_scan_list.equals(required_cols))\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "Some column specified in a Btree " +\r
+ " qualifier/start/stop list is " +\r
+ "not represented in the scanColumnList." +\r
+ "\n:required_cols_and_scan_list = " + \r
+ required_cols_and_scan_list + \r
+ "\n;required_cols = " + required_cols +\r
+ "\n;init_scanColumnList = " + init_scanColumnList);\r
+ }\r
+ }\r
+ } \r
+ }\r
+\r
+ /**\r
+ * Position scan at "start" position for a forward scan.\r
+ * <p> \r
+ * Positions the scan to the slot just before the first record to be \r
+ * returned from the scan. Returns the start page latched, and \r
+ * sets "current_slot" to the slot number.\r
+ * <p>\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected void positionAtStartForForwardScan(\r
+ BTreeRowPosition pos)\r
+ throws StandardException\r
+ {\r
+ boolean exact;\r
+\r
+ // This routine should only be called from first next() call //\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ (scan_state == SCAN_INIT) || (scan_state == SCAN_HOLD_INIT));\r
+ SanityManager.ASSERT(pos.current_rh == null);\r
+ SanityManager.ASSERT(pos.current_positionKey == null);\r
+ SanityManager.ASSERT(pos.current_scan_pageno == 0);\r
+ }\r
+\r
+ // Loop until you can lock the row previous to the first row to be\r
+ // returned by the scan, while holding the page latched, without\r
+ // waiting. If you have to wait, drop the latch, wait for the lock -\r
+ // which makes it likely if you wait for the lock you will loop just\r
+ // once, find the same lock satisfies the search and since you already\r
+ // have the lock it will be granted.\r
+ while (true)\r
+ {\r
+ // Find the starting page and row slot, must start at root and\r
+ // search either for leftmost leaf, or search for specific key.\r
+ ControlRow root = ControlRow.get(this, BTree.ROOTPAGEID); \r
+\r
+ // include search of tree in page visited stats.\r
+ stat_numpages_visited += root.getLevel() + 1;\r
+\r
+ boolean need_previous_lock = true;\r
+\r
+ if (init_startKeyValue == null)\r
+ {\r
+ // No start given, so position at 0 slot of leftmost leaf page\r
+ pos.current_leaf = (LeafControlRow) root.searchLeft(this);\r
+\r
+ pos.current_slot = ControlRow.CR_SLOT;\r
+ exact = false;\r
+ }\r
+ else\r
+ {\r
+ // Search for the starting row.\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(\r
+ (init_startSearchOperator == ScanController.GE) ||\r
+ (init_startSearchOperator == ScanController.GT));\r
+\r
+ SearchParameters sp = new SearchParameters(\r
+ init_startKeyValue, \r
+ ((init_startSearchOperator == ScanController.GE) ? \r
+ SearchParameters.POSITION_LEFT_OF_PARTIAL_KEY_MATCH : \r
+ SearchParameters.POSITION_RIGHT_OF_PARTIAL_KEY_MATCH),\r
+ init_template, this, false);\r
+\r
+ pos.current_leaf = (LeafControlRow) root.search(sp);\r
+\r
+ pos.current_slot = sp.resultSlot;\r
+ exact = sp.resultExact;\r
+\r
+ // The way that scans are used, the caller calls next()\r
+ // to position on the first row. If the result of the\r
+ // search that found the starting page and slot was not\r
+ // exact, then the page/slot will refer to the row before\r
+ // the first qualifying row. The first call to next()\r
+ // will therefore move to the first (potentially) qualifying\r
+ // row. However, if the search was exact, then we don't\r
+ // want to move the position on the first call to next.\r
+ // In that case, by decrementing the slot, the first call\r
+ // to next will put us back on the starting row.\r
+\r
+ if (exact && init_startSearchOperator == ScanController.GE)\r
+ {\r
+ pos.current_slot--;\r
+\r
+ // A scan on a unique index, with a start position of\r
+ // GE, need not get a previous key lock to protect the\r
+ // range. Since it is unique no other key can go before\r
+ // the first row returned from the scan.\r
+ //\r
+ // RESOLVE - currently btree's only support allowDuplicates\r
+ // of "false", so no need to do the extra check, current\r
+ // btree implementation depends on RowLocation field\r
+ // making every key unique (duplicate indexes are supported\r
+ // by the nUniqueColumns and nKeyFields). \r
+ if (getConglomerate().nUniqueColumns < getConglomerate().nKeyFields)\r
+ {\r
+ // this implies unique index, thus no prev key.\r
+ need_previous_lock = false;\r
+ }\r
+ }\r
+ }\r
+\r
+ boolean latch_released = false;\r
+ if (need_previous_lock)\r
+ {\r
+ latch_released = \r
+ !this.getLockingPolicy().lockScanRow(\r
+ this, this.getConglomerate(), pos,\r
+ true, \r
+ init_lock_fetch_desc,\r
+ pos.current_lock_template,\r
+ pos.current_lock_row_loc,\r
+ true, init_forUpdate, \r
+ lock_operation);\r
+ }\r
+ else\r
+ {\r
+ // Don't need to lock the "previous key" but still need to get\r
+ // the scan lock to protect the position in the btree.\r
+\r
+ latch_released =\r
+ !this.getLockingPolicy().lockScan(\r
+ pos.current_leaf, // the page we are positioned on.\r
+ (ControlRow) null, // no other page to unlatch\r
+ false, // lock for read.\r
+ lock_operation); // not used.\r
+ }\r
+\r
+ // special test to see if latch release code works\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ latch_released = \r
+ test_errors(\r
+ this,\r
+ "BTreeScan_positionAtStartPosition", true,\r
+ this.getLockingPolicy(), \r
+ pos.current_leaf, latch_released);\r
+ }\r
+\r
+ if (latch_released)\r
+ {\r
+ // lost latch on pos.current_leaf, search the tree again.\r
+ pos.current_leaf = null;\r
+ continue;\r
+ }\r
+ else\r
+ {\r
+ // success! got all the locks, while holding the latch.\r
+ break;\r
+ }\r
+ }\r
+\r
+ this.scan_state = SCAN_INPROGRESS;\r
+ pos.current_scan_pageno = pos.current_leaf.page.getPageNumber();\r
+ pos.current_slot = pos.current_slot;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(pos.current_leaf != null);\r
+ }\r
+\r
+ /**\r
+ * Position scan at "start" position for a backward scan.\r
+ * <p>\r
+ * Positions the scan to the slot just after the first record to be \r
+ * returned from the backward scan. Returns the start page latched, and \r
+ * sets "current_slot" to the slot number just right of the first slot\r
+ * to return.\r
+ * <p>\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected void positionAtStartForBackwardScan(\r
+ BTreeRowPosition pos)\r
+ throws StandardException\r
+ {\r
+ boolean exact;\r
+\r
+ // This routine should only be called from first next() call //\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ (this.scan_state == SCAN_INIT) || \r
+ (this.scan_state == SCAN_HOLD_INIT));\r
+\r
+ SanityManager.ASSERT(pos.current_rh == null);\r
+ SanityManager.ASSERT(pos.current_positionKey == null);\r
+ SanityManager.ASSERT(pos.current_scan_pageno == 0);\r
+ }\r
+\r
+ // Loop until you can lock the row previous to the first row to be\r
+ // returned by the scan, while holding the page latched, without\r
+ // waiting. If you have to wait, drop the latch, wait for the lock -\r
+ // which makes it likely if you wait for the lock you will loop just\r
+ // once, find the same lock satisfies the search and since you already\r
+ // have the lock it will be granted.\r
+ while (true)\r
+ {\r
+ // Find the starting page and row slot, must start at root and\r
+ // search either for leftmost leaf, or search for specific key.\r
+ ControlRow root = ControlRow.get(this, BTree.ROOTPAGEID); \r
+\r
+ // include search of tree in page visited stats.\r
+ stat_numpages_visited += root.getLevel() + 1;\r
+\r
+ if (init_startKeyValue == null)\r
+ {\r
+ // No start given, position at last slot + 1 of rightmost leaf \r
+ pos.current_leaf = (LeafControlRow) root.searchRight(this);\r
+\r
+ pos.current_slot = pos.current_leaf.page.recordCount();\r
+ exact = false;\r
+ }\r
+ else\r
+ {\r
+ /*\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT(\r
+ "Code not ready yet for positioned backward scans.");\r
+ */\r
+\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(\r
+ (init_startSearchOperator == ScanController.GE) ||\r
+ (init_startSearchOperator == ScanController.GT));\r
+\r
+ // Search for the starting row.\r
+\r
+ SearchParameters sp = new SearchParameters(\r
+ init_startKeyValue, \r
+ ((init_startSearchOperator == ScanController.GE) ? \r
+ SearchParameters.POSITION_RIGHT_OF_PARTIAL_KEY_MATCH : \r
+ SearchParameters.POSITION_LEFT_OF_PARTIAL_KEY_MATCH),\r
+ init_template, this, false);\r
+\r
+ pos.current_leaf = (LeafControlRow) root.search(sp);\r
+\r
+ pos.current_slot = sp.resultSlot;\r
+ exact = sp.resultExact;\r
+\r
+ // The way that backward scans are used, the caller calls next()\r
+ // to position on the first row. If the result of the\r
+ // search that found the starting page and slot was not\r
+ // exact, then the page/slot will refer to the row before\r
+ // the first qualifying row. The first call to next()\r
+ // will therefore move to the first (potentially) qualifying\r
+ // row. However, if the search was exact, then we don't\r
+ // want to move the position on the first call to next.\r
+ // In that case, by decrementing the slot, the first call\r
+ // to next will put us back on the starting row.\r
+\r
+\r
+ if (exact)\r
+ {\r
+ // the search has found exactly the start position key \r
+ if (init_startSearchOperator == ScanController.GE)\r
+ {\r
+ // insure backward scan returns this row by moving\r
+ // slot to one after this row.\r
+ pos.current_slot++;\r
+ }\r
+ else\r
+ {\r
+ // no work necessary leave startslot positioned on the\r
+ // row, we will skip this record\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(\r
+ init_startSearchOperator == ScanController.GT);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // the search positioned one before the start position key,\r
+ // move it to one "after"\r
+ pos.current_slot++;\r
+ }\r
+ }\r
+\r
+ boolean latch_released = \r
+ !this.getLockingPolicy().lockScanRow(\r
+ this, this.getConglomerate(), pos,\r
+ true, \r
+ init_lock_fetch_desc,\r
+ pos.current_lock_template,\r
+ pos.current_lock_row_loc,\r
+ true, init_forUpdate, lock_operation);\r
+\r
+ // special test to see if latch release code works\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ latch_released = \r
+ test_errors(\r
+ this,\r
+ "BTreeScan_positionAtStartPosition", true,\r
+ this.getLockingPolicy(), pos.current_leaf, latch_released);\r
+ }\r
+\r
+ if (latch_released)\r
+ {\r
+ // lost latch on pos.current_leaf, search the tree again.\r
+ pos.current_leaf = null;\r
+ continue;\r
+ }\r
+ else\r
+ {\r
+ // success! got all the locks, while holding the latch.\r
+ break;\r
+ }\r
+ }\r
+\r
+ this.scan_state = SCAN_INPROGRESS;\r
+ pos.current_scan_pageno = pos.current_leaf.page.getPageNumber();\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(pos.current_leaf != null);\r
+\r
+ // System.out.println("backward scan end start position: " +\r
+ // " current_slot = " + this.current_slot );\r
+ }\r
+\r
+ /**\r
+ * Position scan to 0 slot on next page.\r
+ * <p>\r
+ * Position to next page, keeping latch on previous page until we have \r
+ * latch on next page. This routine releases the latch on current_page\r
+ * once it has successfully gotten both the latch on the next page and\r
+ * the scan lock on the next page.\r
+ *\r
+ * @param pos current row position of the scan.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected void positionAtNextPage(\r
+ BTreeRowPosition pos)\r
+ throws StandardException\r
+ {\r
+ // RESOLVE (mikem) - not sure but someday in the future this\r
+ // assert may not be true, but for now we always have the scan\r
+ // lock when we call this routine.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(pos.current_scan_pageno != 0);\r
+\r
+ while (true)\r
+ {\r
+ if ((pos.next_leaf = \r
+ (LeafControlRow) pos.current_leaf.getRightSibling(this)) == null)\r
+ {\r
+ break;\r
+ }\r
+\r
+ boolean latch_released = \r
+ !this.getLockingPolicy().lockScan(\r
+ pos.next_leaf,\r
+ (LeafControlRow) null, // no other latch currently\r
+ false /* not for update */,\r
+ ConglomerateController.LOCK_READ); // get read scan lock.\r
+\r
+ // TESTING CODE:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ latch_released = \r
+ test_errors(\r
+ this,\r
+ "BTreeScan_positionAtNextPage", true,\r
+ this.getLockingPolicy(), pos.next_leaf, latch_released);\r
+ }\r
+\r
+ if (!latch_released)\r
+ {\r
+ break;\r
+ }\r
+ }\r
+\r
+ // Now that we either have both latch and scan lock on next leaf, or \r
+ // there is no next leaf we can release scan and latch on current page.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (pos.current_scan_pageno != pos.current_leaf.page.getPageNumber())\r
+ SanityManager.THROWASSERT(\r
+ "pos.current_scan_pageno = " + pos.current_scan_pageno +\r
+ "pos.current_leaf = " + pos.current_leaf);\r
+ }\r
+\r
+ // unlock the previous row if doing read.\r
+ if (pos.current_rh != null)\r
+ {\r
+ this.getLockingPolicy().unlockScanRecordAfterRead(\r
+ pos, init_forUpdate);\r
+ }\r
+\r
+ this.getLockingPolicy().unlockScan(\r
+ pos.current_leaf.page.getPageNumber());\r
+ pos.current_leaf.release();\r
+ pos.current_leaf = pos.next_leaf;\r
+\r
+ pos.current_scan_pageno = \r
+ (pos.next_leaf == null) ? 0 : pos.next_leaf.page.getPageNumber();\r
+\r
+ // set up for scan to continue at beginning of next page.\r
+ pos.current_slot = Page.FIRST_SLOT_NUMBER;\r
+ pos.current_rh = null;\r
+ }\r
+\r
+ /**\r
+ Position scan at "start" position.\r
+ <p>\r
+ Positions the scan to the slot just before the first record to be returned\r
+ from the scan. Returns the start page latched, and sets "current_slot" to\r
+ the slot number.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ abstract void positionAtStartPosition(\r
+ BTreeRowPosition pos)\r
+ throws StandardException;\r
+\r
+\r
+ /**\r
+ * Do any necessary work to complete the scan.\r
+ *\r
+ * @param pos current row position of the scan.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected void positionAtDoneScanFromClose(\r
+ BTreeRowPosition pos)\r
+ throws StandardException\r
+ {\r
+ // call unlockScanRecordAfterRead() before closing, currently\r
+ // this is only important for releasing RR locks on non-qualified\r
+ // rows. \r
+ //\r
+ // Otherwise the correct behavior happens as part of the close, ie.:\r
+ //\r
+ // for READ_UNCOMMITTED there is no lock to release, \r
+ // for READ_COMMITTED all read locks will be released, \r
+ // for REPEATABLE_READ or SERIALIZABLE no locks are released.\r
+\r
+ if ((pos.current_rh != null) && !pos.current_rh_qualified)\r
+ {\r
+ if (pos.current_leaf == null || pos.current_leaf.page == null)\r
+ {\r
+ // If we are being called from a "normal" close then there\r
+ // will be no latch on current_leaf, get it and do the the\r
+ // unlock. We may be called sometimes, after an error where\r
+ // we may have the latch, in this case the transaction is about\r
+ // to be backed out anyway so don't worry about doing this \r
+ // unlock (thus why we only do the following code if we\r
+ // "don't" have lock, ie. pos.current_leaf== null).\r
+\r
+ if (!reposition(pos, false))\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "can not fail while holding update row lock.");\r
+ }\r
+ }\r
+\r
+ this.getLockingPolicy().unlockScanRecordAfterRead(\r
+ pos, init_forUpdate);\r
+\r
+ pos.current_rh = null;\r
+ pos.current_leaf.release();\r
+ pos.current_leaf = null;\r
+ }\r
+ }\r
+\r
+\r
+ // Need to do this unlock in any case, until lock manager provides\r
+ // a way to release locks associated with a compatibility space. This\r
+ // scan lock is special, as it is a lock on the btree container rather\r
+ // than the heap container. The open container on the btree actually\r
+ // has a null locking policy so the close of that container does not\r
+ // release this lock, need to explicitly unlock it here or when the\r
+ // scan is closed as part of the abort the lock will not be released.\r
+ if (pos.current_scan_pageno != 0)\r
+ {\r
+ this.getLockingPolicy().unlockScan(pos.current_scan_pageno);\r
+ pos.current_scan_pageno = 0;\r
+ }\r
+\r
+ pos.current_slot = Page.INVALID_SLOT_NUMBER;\r
+ pos.current_rh = null;\r
+ pos.current_positionKey = null;\r
+ this.scan_state = SCAN_DONE;\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * Do work necessary to close a scan.\r
+ * <p>\r
+ * This routine can only be called "inline" from other btree routines,\r
+ * as it counts on the state of the pos to be correct.\r
+ * <p>\r
+ * Closing a scan from close() must handle long jumps from exceptions\r
+ * where the state of pos may not be correct. The easiest case is\r
+ * a lock timeout which has caused us not to have a latch on a page,\r
+ * but pos still thinks there is a latch. This is the easiest but\r
+ * other exceptions can also caused the same state at close() time.\r
+ **/\r
+ protected void positionAtDoneScan(\r
+ BTreeRowPosition pos)\r
+ throws StandardException\r
+ {\r
+\r
+ // Need to do this unlock in any case, until lock manager provides\r
+ // a way to release locks associated with a compatibility space. This\r
+ // scan lock is special, as it is a lock on the btree container rather\r
+ // than the heap container. The open container on the btree actually\r
+ // has a null locking policy so the close of that container does not\r
+ // release this lock, need to explicitly unlock it here or when the\r
+ // scan is closed as part of the abort the lock will not be released.\r
+ if (pos.current_scan_pageno != 0)\r
+ {\r
+ this.getLockingPolicy().unlockScan(pos.current_scan_pageno);\r
+ pos.current_scan_pageno = 0;\r
+ }\r
+\r
+ pos.current_slot = Page.INVALID_SLOT_NUMBER;\r
+ pos.current_rh = null;\r
+ pos.current_positionKey = null;\r
+ this.scan_state = SCAN_DONE;\r
+\r
+ return;\r
+ }\r
+\r
+\r
+ /**\r
+ * process_qualifier - Determine if a row meets all qualifier conditions.\r
+ * <p>\r
+ * Check all qualifiers in the qualifier array against row. Return true\r
+ * if all compares specified by the qualifier array return true, else\r
+ * return false.\r
+ * <p>\r
+ * It is up to caller to make sure qualifier list is non-null.\r
+ *\r
+ * @param row The row with the same partial column list as the\r
+ * row returned by the current scan.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ */\r
+ protected boolean process_qualifier(\r
+ DataValueDescriptor[] row) \r
+ throws StandardException\r
+ {\r
+ boolean row_qualifies = true;\r
+ Qualifier q;\r
+\r
+ // Process the 2-d qualifier which is structured as follows:\r
+ //\r
+ // A two dimensional array is to be used to pass around a AND's and OR's\r
+ // in conjunctive normal form (CNF). The top slot of the 2 dimensional\r
+ // array is optimized for the more frequent where no OR's are present. \r
+ // The first array slot is always a list of AND's to be treated as \r
+ // described above for single dimensional AND qualifier arrays. The \r
+ // subsequent slots are to be treated as AND'd arrays or OR's. Thus \r
+ // the 2 dimensional array qual[][] argument is to be treated as the \r
+ // following, note if qual.length = 1 then only the first array is \r
+ // valid and // it is and an array of and clauses:\r
+ //\r
+ // (qual[0][0] and qual[0][0] ... and qual[0][qual[0].length - 1])\r
+ // and\r
+ // (qual[1][0] or qual[1][1] ... or qual[1][qual[1].length - 1])\r
+ // and\r
+ // (qual[2][0] or qual[2][1] ... or qual[2][qual[2].length - 1])\r
+ // ...\r
+ // and\r
+ // (qual[qual.length - 1][0] or qual[1][1] ... or qual[1][2])\r
+\r
+ // First do the qual[0] which is an array of qualifer terms.\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // routine should not be called if there is no qualifier\r
+ SanityManager.ASSERT(this.init_qualifier != null);\r
+ SanityManager.ASSERT(this.init_qualifier.length > 0);\r
+ }\r
+\r
+ for (int i = 0; i < this.init_qualifier[0].length; i++)\r
+ {\r
+ // process each AND clause \r
+\r
+ row_qualifies = false;\r
+\r
+ // process each OR clause.\r
+\r
+ q = this.init_qualifier[0][i];\r
+\r
+ // Get the column from the possibly partial row, of the \r
+ // q.getColumnId()'th column in the full row.\r
+ DataValueDescriptor columnValue = row[q.getColumnId()];\r
+\r
+ row_qualifies =\r
+ columnValue.compare(\r
+ q.getOperator(),\r
+ q.getOrderable(),\r
+ q.getOrderedNulls(),\r
+ q.getUnknownRV());\r
+\r
+ if (q.negateCompareResult())\r
+ row_qualifies = !row_qualifies;\r
+\r
+ // Once an AND fails the whole Qualification fails - do a return!\r
+ if (!row_qualifies)\r
+ return(false);\r
+ }\r
+\r
+ // all the qual[0] and terms passed, now process the OR clauses\r
+\r
+ for (int and_idx = 1; and_idx < this.init_qualifier.length; and_idx++)\r
+ {\r
+ // process each AND clause \r
+\r
+ row_qualifies = false;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // Each OR clause must be non-empty.\r
+ SanityManager.ASSERT(this.init_qualifier[and_idx].length > 0);\r
+ }\r
+\r
+ for (int or_idx = 0; \r
+ or_idx < this.init_qualifier[and_idx].length; or_idx++)\r
+ {\r
+ // process each OR clause.\r
+\r
+ q = this.init_qualifier[and_idx][or_idx];\r
+\r
+ // Get the column from the possibly partial row, of the \r
+ // q.getColumnId()'th column in the full row.\r
+ DataValueDescriptor columnValue = row[q.getColumnId()];\r
+\r
+ row_qualifies =\r
+ columnValue.compare(\r
+ q.getOperator(),\r
+ q.getOrderable(),\r
+ q.getOrderedNulls(),\r
+ q.getUnknownRV());\r
+\r
+ if (q.negateCompareResult())\r
+ row_qualifies = !row_qualifies;\r
+\r
+ // once one OR qualifies the entire clause is TRUE\r
+ if (row_qualifies)\r
+ break;\r
+ }\r
+\r
+ if (!row_qualifies)\r
+ break;\r
+ }\r
+\r
+ return(row_qualifies);\r
+ }\r
+\r
+\r
+ /**\r
+ * Reposition the scan leaving and reentering the access layer.\r
+ * <p>\r
+ * When a scan leaves access it saves the RecordHandle of the record\r
+ * on the page. There are 2 cases to consider when trying to reposition\r
+ * the scan when re-entering access:\r
+ * o ROW has not moved off the page.\r
+ * If the row has not moved then the RecordHandle we have saved\r
+ * away is valid, and we just call RawStore to reposition on that\r
+ * RecordHandle (RawStore takes care of the row moving within\r
+ * the page).\r
+ * o ROW has moved off the page.\r
+ * This can only happen in the case of a btree split. In that\r
+ * case the splitter will have caused all scans positioned on \r
+ * this page within the same transaction to save a copy of the\r
+ * row that the scan was positioned on. Then to reposition the\r
+ * scan it is necessary to research the tree from the top using\r
+ * the copy of the row.\r
+ *\r
+ * If the scan has saved it's position by key (and thus has given up the\r
+ * scan lock on the page), there are a few cases where it is possible that\r
+ * the key no longer exists in the table. In the case of a scan held \r
+ * open across commit it is easy to imagine that the row the scan was \r
+ * positioned on could be deleted and subsequently purged from the table \r
+ * all before the scan resumes. Also in the case of read uncommitted \r
+ * the scan holds no lock on the current row, so it could be purged -\r
+ * in the following scenario for instance: read uncommitted transaction 1\r
+ * opens scan and positions on row (1,2), transaction 2 deletes (1,2) and\r
+ * commits, transaction 1 inserts (1,3) which goes to same page as (1,2)\r
+ * and is going to cause a split, transaction 1 saves scan position as\r
+ * key, gives up scan lock and then purges row (1, 2), when transaction \r
+ * 1 resumes scan (1, 2) no longer exists. missing_row_for_key_ok \r
+ * parameter is added as a sanity check to make sure it ok that \r
+ * repositioning does not go to same row that we were repositioned on.\r
+ *\r
+ *\r
+ *\r
+ * @param pos position to set the scan to.\r
+ *\r
+ * @param missing_row_for_key_ok if true and exact key is not found then\r
+ * scan is just set to key just left of\r
+ * the key (thus a next will move to the\r
+ * key just after "pos")\r
+ *\r
+ * @return returns true if scan has been repositioned successfully, else\r
+ * returns false if the position key could not be found and\r
+ * missing_row_for_key_ok was false indicating that scan could\r
+ * only be positioned on the exact key match.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected boolean reposition(\r
+ BTreeRowPosition pos,\r
+ boolean missing_row_for_key_ok)\r
+ throws StandardException\r
+ {\r
+ // RESOLVE (mikem) - performance - we need to do a buffer manager\r
+ // get for every row returned from the scan. It may be better to\r
+ // allow a reference to the page with no latch (ie. a fixed bit).\r
+\r
+ if (this.scan_state != SCAN_INPROGRESS)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_SCAN_NOT_POSITIONED, \r
+ new Integer(this.scan_state));\r
+ }\r
+\r
+ // Either current_rh or positionKey is valid - the other is null.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if ((pos.current_rh == null) != (pos.current_positionKey != null))\r
+ SanityManager.THROWASSERT(\r
+ "pos.current_rh = (" + pos.current_rh + "), " +\r
+ "pos.current_positionKey = (" + \r
+ pos.current_positionKey + ").");\r
+ }\r
+\r
+ if (!((pos.current_rh == null) == (pos.current_positionKey != null)))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_SCAN_INTERNAL_ERROR, \r
+ new Boolean(pos.current_rh == null), \r
+ new Boolean(pos.current_positionKey == null));\r
+ }\r
+\r
+ if (pos.current_positionKey == null)\r
+ {\r
+ // Reposition to remembered spot on page.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(pos.current_scan_pageno != 0);\r
+\r
+ pos.current_leaf = (LeafControlRow)\r
+ ControlRow.get(this, pos.current_rh.getPageNumber());\r
+ pos.current_slot =\r
+ pos.current_leaf.page.getSlotNumber(pos.current_rh);\r
+ }\r
+ else\r
+ {\r
+ // RESOLVE (mikem) - not sure but someday in the future this\r
+ // assert may not be true, but for now we always release the \r
+ // scan lock when we save the row away as the current position.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(pos.current_scan_pageno == 0);\r
+\r
+ SearchParameters sp =\r
+ new SearchParameters(\r
+ pos.current_positionKey, \r
+ // this is a full key search, so this arg is not used.\r
+ SearchParameters.POSITION_LEFT_OF_PARTIAL_KEY_MATCH,\r
+ init_template, this, false);\r
+\r
+ // latch/lock loop, continue until you can get scan lock on page\r
+ // while holding page latched without waiting.\r
+\r
+\r
+ boolean latch_released;\r
+ do\r
+ {\r
+ pos.current_leaf = (LeafControlRow)\r
+ ControlRow.get(this, BTree.ROOTPAGEID).search(sp);\r
+\r
+ if (sp.resultExact || missing_row_for_key_ok)\r
+ {\r
+ // RESOLVE (mikem) - we could have a scan which always \r
+ // maintained it's position by key value, or we could \r
+ // optimize and delay this lock until we were about to \r
+ // give up the latch. But it is VERY likely we will get \r
+ // the lock since we have the latch on the page.\r
+ //\r
+ // In order to be successfully positioned we must get the \r
+ // scan lock again.\r
+ latch_released = \r
+ !this.getLockingPolicy().lockScan(\r
+ pos.current_leaf, \r
+ (LeafControlRow) null, // no other latch currently\r
+ false /* not for update */,\r
+ ConglomerateController.LOCK_READ); // read lock on scan position\r
+\r
+ // TESTING CODE:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ latch_released = \r
+ test_errors(\r
+ this,\r
+ "BTreeScan_reposition", true, \r
+ this.getLockingPolicy(),\r
+ pos.current_leaf, latch_released);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // Did not find key to exactly position on.\r
+\r
+ pos.current_leaf.release();\r
+ pos.current_leaf = null;\r
+ return(false);\r
+ }\r
+\r
+ } while (latch_released);\r
+\r
+ pos.current_scan_pageno = pos.current_leaf.page.getPageNumber();\r
+ pos.current_slot = sp.resultSlot;\r
+ pos.current_positionKey = null;\r
+ }\r
+\r
+ return(true);\r
+ }\r
+\r
+ /*\r
+ ** Public Methods of BTreeScan\r
+ */\r
+\r
+\r
+ /**\r
+ Initialize the scan for use.\r
+ <p>\r
+ Any changes to this method may have to be reflected in close as well.\r
+ <p>\r
+ The btree init opens the container (super.init), and stores away the\r
+ state of the qualifiers. The actual searching for the first position\r
+ is delayed until the first next() call.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public void init(\r
+ TransactionManager xact_manager,\r
+ Transaction rawtran,\r
+ boolean hold,\r
+ int open_mode,\r
+ int lock_level,\r
+ BTreeLockingPolicy btree_locking_policy,\r
+ FormatableBitSet scanColumnList,\r
+ DataValueDescriptor[] startKeyValue,\r
+ int startSearchOperator,\r
+ Qualifier qualifier[][],\r
+ DataValueDescriptor[] stopKeyValue,\r
+ int stopSearchOperator,\r
+ BTree conglomerate,\r
+ LogicalUndo undo,\r
+ StaticCompiledOpenConglomInfo static_info,\r
+ DynamicCompiledOpenConglomInfo dynamic_info)\r
+ throws StandardException\r
+ {\r
+ super.init(\r
+ xact_manager, xact_manager, (ContainerHandle) null, rawtran,\r
+ hold,\r
+ open_mode, lock_level, btree_locking_policy, \r
+ conglomerate, undo, dynamic_info);\r
+\r
+\r
+ this.init_rawtran = rawtran;\r
+ this.init_forUpdate = \r
+ ((open_mode & ContainerHandle.MODE_FORUPDATE) == \r
+ ContainerHandle.MODE_FORUPDATE);\r
+\r
+ // Keep track of whether this scan should use update locks.\r
+ this.init_useUpdateLocks = \r
+ ((open_mode &\r
+ ContainerHandle.MODE_USE_UPDATE_LOCKS) != 0);\r
+\r
+ this.init_hold = hold;\r
+\r
+ this.init_template = \r
+ runtime_mem.get_template(getRawTran());\r
+\r
+ this.init_scanColumnList = scanColumnList;\r
+\r
+ this.init_lock_fetch_desc = \r
+ RowUtil.getFetchDescriptorConstant(init_template.length - 1);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ init_lock_fetch_desc.getMaxFetchColumnId() == \r
+ (init_template.length - 1));\r
+ SanityManager.ASSERT(\r
+ (init_lock_fetch_desc.getValidColumnsArray())[init_template.length - 1] == 1); \r
+ }\r
+\r
+ // note that we don't process qualifiers in btree fetch's\r
+ this.init_fetchDesc = \r
+ new FetchDescriptor(\r
+ init_template.length, init_scanColumnList,(Qualifier[][]) null);\r
+\r
+ initScanParams( \r
+ startKeyValue, startSearchOperator, \r
+ qualifier, stopKeyValue, stopSearchOperator);\r
+\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ TemplateRow.checkColumnTypes(\r
+ getRawTran().getDataValueFactory(),\r
+ this.getConglomerate().format_ids, \r
+ this.getConglomerate().collation_ids,\r
+ init_template));\r
+ }\r
+\r
+ // System.out.println("initializing scan:" + this);\r
+\r
+ // initialize default locking operation for the scan.\r
+ this.lock_operation = \r
+ (init_forUpdate ? \r
+ ConglomerateController.LOCK_UPD : \r
+ ConglomerateController.LOCK_READ);\r
+\r
+ if (init_useUpdateLocks)\r
+ this.lock_operation |= ConglomerateController.LOCK_UPDATE_LOCKS;\r
+\r
+ // System.out.println("Btree scan: " + this);\r
+ }\r
+\r
+\r
+\r
+ /*\r
+ ** Methods of ScanController\r
+ */\r
+\r
+ /**\r
+ Close the scan.\r
+ **/\r
+ public void close()\r
+ throws StandardException\r
+ {\r
+ // Scan is closed, make sure no access to any state variables\r
+ positionAtDoneScanFromClose(scan_position);\r
+\r
+ super.close();\r
+\r
+ // null out so that these object's can get GC'd earlier.\r
+ this.init_rawtran = null;\r
+ this.init_template = null;\r
+ this.init_startKeyValue = null;\r
+ this.init_qualifier = null;\r
+ this.init_stopKeyValue = null;\r
+\r
+ this.getXactMgr().closeMe(this);\r
+ }\r
+\r
+ /**\r
+ Delete the row at the current position of the scan.\r
+ @see ScanController#delete\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean delete()\r
+ throws StandardException\r
+ {\r
+ boolean ret_val = false;\r
+\r
+ if (scan_state != SCAN_INPROGRESS)\r
+ throw StandardException.newException(\r
+ SQLState.AM_SCAN_NOT_POSITIONED);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(this.container != null,\r
+ "BTreeScan.delete() called on a closed scan.");\r
+ SanityManager.ASSERT(init_forUpdate);\r
+ }\r
+\r
+ try\r
+ {\r
+ // Get current page of scan, with latch.\r
+ if (!reposition(scan_position, false))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.AM_RECORD_NOT_FOUND,\r
+ new Long(err_containerid),\r
+ new Long(scan_position.current_rh.getId()));\r
+ }\r
+\r
+\r
+ if (init_useUpdateLocks)\r
+ {\r
+ // RESOLVE (mikem) - I don't think lockScanRow() is the right\r
+ // thing to call.\r
+\r
+ // if we are doing update locking, then we got an U lock on\r
+ // this row when the scan positioned on it, but now that we\r
+ // are doing a delete on the current position we need to upgrade\r
+ // the lock to X.\r
+ boolean latch_released =\r
+ !this.getLockingPolicy().lockScanRow(\r
+ this, this.getConglomerate(), scan_position,\r
+ false, \r
+ init_lock_fetch_desc,\r
+ scan_position.current_lock_template,\r
+ scan_position.current_lock_row_loc,\r
+ false, init_forUpdate, lock_operation);\r
+\r
+ if (latch_released)\r
+ {\r
+ // lost latch on page in order to wait for row lock.\r
+ // Because we have scan lock on page, we need only\r
+ // call reposition() which will use the saved record\r
+ // handle to reposition to the same spot on the page.\r
+ // We don't have to search the\r
+ // tree again, as we have the a scan lock on the page\r
+ // which means the current_rh is valid to reposition on.\r
+ if (reposition(scan_position, false))\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.AM_RECORD_NOT_FOUND,\r
+ new Long(err_containerid),\r
+ new Long(scan_position.current_rh.getId()));\r
+ }\r
+ }\r
+ }\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ // DERBY-2197: Assume no row locking here. If locking policy\r
+ // requires row locking, we would need to obtain a row lock at\r
+ // this point.\r
+ SanityManager.ASSERT(\r
+ (container.getLockingPolicy().getMode() !=\r
+ LockingPolicy.MODE_RECORD),\r
+ "Locking policy requires row locking.");\r
+ }\r
+\r
+ if (scan_position.current_leaf.page.isDeletedAtSlot(\r
+ scan_position.current_slot)) \r
+ {\r
+ ret_val = false;\r
+ } \r
+ else \r
+ {\r
+ scan_position.current_leaf.page.deleteAtSlot(\r
+ scan_position.current_slot, true, this.btree_undo);\r
+ ret_val = true;\r
+ }\r
+\r
+ // See if we just deleted the last row on the page, in a btree a\r
+ // page with all rows still has 1 left - the control row.\r
+ // Do not reclaim the root page of the btree if there are no \r
+ // children since we were doing too many post commit actions in a \r
+ // benchmark which does an insert/commit/delete/commit operations \r
+ // in a single user system. Now with this change the work will \r
+ // move to the user thread which does the insert and finds no space\r
+ // on the root page. In that case it will try a split, which \r
+ // automatically first checks if there is committed deleted space\r
+ // that can be reclaimed.\r
+\r
+ if (scan_position.current_leaf.page.nonDeletedRecordCount() == 1 &&\r
+ !(scan_position.current_leaf.getIsRoot() && \r
+ scan_position.current_leaf.getLevel() == 0)) \r
+ {\r
+ this.getXactMgr().addPostCommitWork(new BTreePostCommit(\r
+ this.getXactMgr().getAccessManager(),\r
+ this.getConglomerate(),\r
+ scan_position.current_leaf.page.getPageNumber()));\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ if (scan_position.current_leaf != null)\r
+ {\r
+ // release latch on page\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+ }\r
+\r
+ return(ret_val);\r
+ }\r
+\r
+ /**\r
+ * A call to allow client to indicate that current row does not qualify.\r
+ * <p>\r
+ * Indicates to the ScanController that the current row does not\r
+ * qualify for the scan. If the isolation level of the scan allows, \r
+ * this may result in the scan releasing the lock on this row.\r
+ * <p>\r
+ * Note that some scan implimentations may not support releasing locks on \r
+ * non-qualifying rows, or may delay releasing the lock until sometime\r
+ * later in the scan (ie. it may be necessary to keep the lock until \r
+ * either the scan is repositioned on the next row or page).\r
+ * <p>\r
+ * This call should only be made while the scan is positioned on a current\r
+ * valid row.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public void didNotQualify()\r
+ throws StandardException\r
+ {\r
+ }\r
+\r
+\r
+ /**\r
+ * Returns true if the current position of the scan still qualifies\r
+ * under the set of qualifiers passed to the openScan(). When called\r
+ * this routine will reapply all qualifiers against the row currently\r
+ * positioned and return true if the row still qualifies. If the row\r
+ * has been deleted or no longer passes the qualifiers then this routine\r
+ * will return false.\r
+ * <p>\r
+ * This case can come about if the current scan\r
+ * or another scan on the same table in the same transaction\r
+ * deleted the row or changed columns referenced by the qualifier after\r
+ * the next() call which positioned the scan at this row.\r
+ * <p>\r
+ * Note that for comglomerates which don't support update, like btree's,\r
+ * there is no need to recheck the qualifiers.\r
+ * <p>\r
+ * The results of a fetch() performed on a scan positioned on\r
+ * a deleted row are undefined.\r
+ * <p>\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean doesCurrentPositionQualify()\r
+ throws StandardException\r
+ {\r
+ if (scan_state != SCAN_INPROGRESS)\r
+ throw StandardException.newException(\r
+ SQLState.AM_SCAN_NOT_POSITIONED);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(this.container != null,\r
+ "BTreeScan.doesCurrentPositionQualify() called on a closed scan.");\r
+ }\r
+\r
+ try\r
+ {\r
+ // Get current page of scan, with latch\r
+ if (!reposition(scan_position, false))\r
+ {\r
+ // TODO - write unit test to get here, language always calls\r
+ // isCurrentPositionDeleted() right before calling this, so\r
+ // hard to write .sql test to exercise this.\r
+\r
+ // if reposition fails it means the position of the scan\r
+ // has been purged from the table - for example if this is\r
+ // a uncommitted read scan and somehow the row was purged \r
+ // since the last positioning.\r
+\r
+ return(false);\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ scan_position.current_leaf.page.fetchNumFieldsAtSlot(\r
+ scan_position.current_slot) > 1);\r
+ }\r
+\r
+ // Since btree row don't get updated, the only way a current\r
+ // position may not qualify is if it got deleted.\r
+ return(\r
+ !scan_position.current_leaf.page.isDeletedAtSlot(\r
+ scan_position.current_slot));\r
+ }\r
+ finally\r
+ {\r
+\r
+ if (scan_position.current_leaf != null)\r
+ {\r
+ // release latch on page.\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Fetch the row at the current position of the Scan.\r
+ * \r
+ * @param row The row into which the value of the current \r
+ * position in the scan is to be stored.\r
+ * @param qualify indicates whether the qualifiers should be applied.\r
+ * \r
+ * @exception StandardException Standard exception policy.\r
+ */\r
+ private void fetch(DataValueDescriptor[] row, boolean qualify)\r
+ throws StandardException\r
+ {\r
+ if (scan_state != SCAN_INPROGRESS)\r
+ throw StandardException.newException(\r
+ SQLState.AM_SCAN_NOT_POSITIONED);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(this.container != null,\r
+ "BTreeScan.fetch() called on a closed scan.");\r
+ \r
+ TemplateRow.checkPartialColumnTypes(\r
+ this.getConglomerate().format_ids, \r
+ init_scanColumnList, (int []) null, row);\r
+ }\r
+\r
+ try\r
+ {\r
+ // Get current page of scan, with latch\r
+ if (!reposition(scan_position, false))\r
+ {\r
+ // TODO - write unit test to get here, language always calls\r
+ // isCurrentPositionDeleted() right before calling this, so\r
+ // hard to write .sql test to exercise this.\r
+\r
+ throw StandardException.newException(\r
+ SQLState.AM_RECORD_NOT_FOUND,\r
+ new Long(err_containerid),\r
+ new Long(scan_position.current_rh.getId()));\r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ scan_position.current_leaf.page.fetchNumFieldsAtSlot(\r
+ scan_position.current_slot) > 1);\r
+ }\r
+\r
+ scan_position.current_rh = \r
+ scan_position.current_leaf.page.fetchFromSlot(\r
+ (RecordHandle) null, \r
+ scan_position.current_slot, row, \r
+ qualify ? init_fetchDesc : null,\r
+ true);\r
+\r
+ // The possibility is that the row at the current position\r
+ // has been marked as deleted (it cannot have been purged\r
+ // since the scan maintains a lock on the row, and purges\r
+ // are always done from system transactions). I'm not sure\r
+ // what the desired behavior is in this case. For now,\r
+ // just return null.\r
+\r
+ // RESOLVE (mikem) - what should be done here?\r
+ if (scan_position.current_leaf.page.isDeletedAtSlot(\r
+ scan_position.current_slot))\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(false, "positioned on deleted row");\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ if (scan_position.current_leaf != null)\r
+ {\r
+ // release latch on page.\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ /**\r
+ * @see org.apache.derby.iapi.store.access.ScanController#isHeldAfterCommit\r
+ */\r
+ public boolean isHeldAfterCommit() throws StandardException\r
+ {\r
+ return (scan_state == SCAN_HOLD_INIT ||\r
+ scan_state == SCAN_HOLD_INPROGRESS);\r
+ }\r
+\r
+ /**\r
+ Fetch the row at the current position of the Scan.\r
+ @see ScanController#fetch\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public void fetch(DataValueDescriptor[] row)\r
+ throws StandardException\r
+ {\r
+ fetch(row, true);\r
+ }\r
+\r
+ /**\r
+ * Fetch the row at the current position of the Scan without applying the \r
+ * qualifiers.\r
+ * @see ScanController#fetchWithoutQualify\r
+ * \r
+ * @exception StandardException Standard exception policy.\r
+ */\r
+ public void fetchWithoutQualify(DataValueDescriptor[] row)\r
+ throws StandardException\r
+ {\r
+ fetch(row, false);\r
+ }\r
+ \r
+ /**\r
+ * Return ScanInfo object which describes performance of scan.\r
+ * <p>\r
+ * Return ScanInfo object which contains information about the current\r
+ * scan.\r
+ * <p>\r
+ *\r
+ * @see ScanInfo\r
+ *\r
+ * @return The ScanInfo object which contains info about current scan.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public ScanInfo getScanInfo()\r
+ throws StandardException\r
+ {\r
+ return(new BTreeScanInfo(this));\r
+ }\r
+\r
+ /**\r
+ Returns true if the current position of the scan is at a\r
+ deleted row. This case can come about if the current scan\r
+ or another scan on the same table in the same transaction\r
+ deleted the row after the next() call which positioned the\r
+ scan at this row.\r
+\r
+ The results of a fetch() performed on a scan positioned on\r
+ a deleted row are undefined.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean isCurrentPositionDeleted()\r
+ throws StandardException\r
+ {\r
+ boolean ret_val;\r
+\r
+ if (scan_state != SCAN_INPROGRESS)\r
+ throw StandardException.newException(\r
+ SQLState.AM_SCAN_NOT_POSITIONED);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(this.container != null,\r
+ "BTreeScan.isCurrentPositionDeleted() called on closed scan.");\r
+ }\r
+ try\r
+ {\r
+ // Get current page of scan, with latch\r
+\r
+ if (reposition(scan_position, false))\r
+ {\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ scan_position.current_leaf.page.fetchNumFieldsAtSlot(\r
+ scan_position.current_slot) > 1);\r
+ }\r
+\r
+ ret_val = \r
+ scan_position.current_leaf.page.isDeletedAtSlot(\r
+ scan_position.current_slot);\r
+ }\r
+ else\r
+ {\r
+ ret_val = false;\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ if (scan_position.current_leaf != null)\r
+ {\r
+ // release latch on page.\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+ }\r
+\r
+ return(ret_val);\r
+ }\r
+\r
+ /**\r
+ * Return whether this is a keyed conglomerate.\r
+ * <p>\r
+ *\r
+ * @return whether this is a keyed conglomerate.\r
+ **/\r
+ public boolean isKeyed()\r
+ {\r
+ return(true);\r
+ }\r
+\r
+ /**\r
+ * @see ScanController#positionAtRowLocation\r
+ *\r
+ * Not implemented for this class\r
+ */\r
+ public boolean positionAtRowLocation (RowLocation rLoc) \r
+ throws StandardException \r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE); \r
+ }\r
+\r
+ /**\r
+ Move to the next position in the scan.\r
+ @see ScanController#next\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean next()\r
+ throws StandardException\r
+ {\r
+ // Turn this call into a group fetch of a 1 element group.\r
+ fetchNext_one_slot_array[0] = runtime_mem.get_scratch_row(getRawTran());\r
+ boolean ret_val = \r
+ fetchRows(\r
+ scan_position,\r
+ fetchNext_one_slot_array, \r
+ (RowLocation[]) null,\r
+ (BackingStoreHashtable) null,\r
+ 1,\r
+ (int[]) null) == 1;\r
+\r
+\r
+ return(ret_val);\r
+ }\r
+\r
+ /**\r
+ Fetch the row at the next position of the Scan.\r
+\r
+ If there is a valid next position in the scan then\r
+ the value in the template storable row is replaced\r
+ with the value of the row at the current scan\r
+ position. The columns of the template row must\r
+ be of the same type as the actual columns in the\r
+ underlying conglomerate.\r
+\r
+ The resulting contents of templateRow after a fetchNext() \r
+ which returns false is undefined.\r
+\r
+ The result of calling fetchNext(row) is exactly logically\r
+ equivalent to making a next() call followed by a fetch(row)\r
+ call. This interface allows implementations to optimize \r
+ the 2 calls if possible.\r
+\r
+ @param row The template row into which the value\r
+ of the next position in the scan is to be stored.\r
+\r
+ @return True if there is a next position in the scan,\r
+ false if there isn't.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean fetchNext(DataValueDescriptor[] row)\r
+ throws StandardException\r
+ {\r
+ boolean ret_val;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ TemplateRow.checkPartialColumnTypes(\r
+ this.getConglomerate().format_ids, \r
+ init_scanColumnList, (int[]) null, row);\r
+ }\r
+\r
+ // Turn this call into a group fetch of a 1 element group.\r
+ fetchNext_one_slot_array[0] = row;\r
+ ret_val = \r
+ fetchRows(\r
+ scan_position,\r
+ fetchNext_one_slot_array, \r
+ (RowLocation[]) null,\r
+ (BackingStoreHashtable) null,\r
+ 1,\r
+ (int[]) null) == 1;\r
+\r
+ return(ret_val);\r
+ }\r
+\r
+ /**\r
+ * Fetch the next N rows from the table.\r
+ * <p>\r
+ * The client allocates an array of N rows and passes it into the\r
+ * fetchNextSet() call. This routine does the equivalent of N \r
+ * fetchNext() calls, filling in each of the rows in the array.\r
+ * Locking is performed exactly as if the N fetchNext() calls had\r
+ * been made.\r
+ * <p>\r
+ * It is up to Access how many rows to return. fetchNextSet() will\r
+ * return how many rows were filled in. If fetchNextSet() returns 0\r
+ * then the scan is complete, (ie. the scan is in the same state as if\r
+ * fetchNext() had returned false). If the scan is not complete then\r
+ * fetchNext() will return (1 <= row_count <= N).\r
+ * <p>\r
+ * The current position of the scan is undefined if fetchNextSet()\r
+ * is used (ie. mixing fetch()/fetchNext() and fetchNextSet() calls\r
+ * in a single scan does not work). This is because a fetchNextSet()\r
+ * request for 5 rows from a heap where the first 2 rows qualify, but\r
+ * no other rows qualify will result in the scan being positioned at\r
+ * the end of the table, while if 5 rows did qualify the scan will be\r
+ * positioned on the 5th row.\r
+ * <p>\r
+ * Qualifiers, start and stop positioning of the openscan are applied\r
+ * just as in a normal scan. \r
+ * <p>\r
+ * The columns of the row will be the standard columns returned as\r
+ * part of a scan, as described by the validColumns - see openScan for\r
+ * description.\r
+ * <p>\r
+ * Expected usage:\r
+ *\r
+ * // allocate an array of 5 empty row templates\r
+ * DataValueDescriptor[][] row_array = allocate_row_array(5);\r
+ * int row_cnt = 0;\r
+ *\r
+ * scan = openScan();\r
+ *\r
+ * while ((row_cnt = scan.fetchNextSet(row_array) != 0)\r
+ * {\r
+ * // I got "row_cnt" rows from the scan. These rows will be\r
+ * // found in row_array[0] through row_array[row_cnt - 1]\r
+ * }\r
+ *\r
+ * <p>\r
+ *\r
+ * RESOLVE - This interface is being provided so that we can prototype\r
+ * the performance results it can achieve. If it looks like\r
+ * this interface is useful, it is very likely we will look\r
+ * into a better way to tie together the now 4 different\r
+ * fetch interfaces: fetch, fetchNext(), fetchNextGroup(),\r
+ * and fetchSet().\r
+ *\r
+ * @return The number of qualifying rows found and copied into the \r
+ * provided array of rows. If 0 then the scan is complete, \r
+ * otherwise the return value will be: \r
+ * 1 <= row_count <= row_array.length\r
+ *\r
+ * @param row_array The array of rows to copy rows into. \r
+ * row_array[].length must >= 1. This routine\r
+ * assumes that all entries in the array \r
+ * contain complete template rows.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public int fetchNextGroup(\r
+ DataValueDescriptor[][] row_array,\r
+ RowLocation[] rowloc_array)\r
+ throws StandardException\r
+ {\r
+ return(\r
+ fetchRows(\r
+ scan_position,\r
+ row_array, \r
+ rowloc_array,\r
+ (BackingStoreHashtable) null,\r
+ row_array.length,\r
+ (int[]) null));\r
+ }\r
+\r
+ public int fetchNextGroup(\r
+ DataValueDescriptor[][] row_array,\r
+ RowLocation[] old_rowloc_array,\r
+ RowLocation[] new_rowloc_array)\r
+ throws StandardException\r
+ {\r
+ // This interface is currently only used to move rows around in\r
+ // a heap table, unused in btree's -- so not implemented.\r
+\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE);\r
+ }\r
+\r
+ /**\r
+ * Insert all rows that qualify for the current scan into the input\r
+ * Hash table. \r
+ * <p>\r
+ * This routine scans executes the entire scan as described in the \r
+ * openScan call. For every qualifying unique row value an entry is\r
+ * placed into the HashTable. For unique row values the entry in the\r
+ * BackingStoreHashtable has a key value of the object stored in \r
+ * row[key_column_number], and the value of the data is row. For row \r
+ * values with duplicates, the key value is also row[key_column_number], \r
+ * but the value of the data is a Vector of\r
+ * rows. The caller will have to call "instanceof" on the data value\r
+ * object if duplicates are expected, to determine if the data value\r
+ * of the Hashtable entry is a row or is a Vector of rows.\r
+ * <p>\r
+ * Note, that for this routine to work efficiently the caller must \r
+ * ensure that the object in row[key_column_number] implements \r
+ * the hashCode and equals method as appropriate for it's datatype.\r
+ * <p>\r
+ * It is expected that this call will be the first and only call made in\r
+ * an openscan. Qualifiers and stop position of the openscan are applied\r
+ * just as in a normal scan. This call is logically equivalent to the \r
+ * caller performing the following:\r
+ *\r
+ * import java.util.Hashtable;\r
+ *\r
+ * hash_table = new Hashtable();\r
+ *\r
+ * while (next())\r
+ * {\r
+ * row = create_new_row();\r
+ * fetch(row);\r
+ * if ((duplicate_value = \r
+ * hash_table.put(row[key_column_number], row)) != null)\r
+ * {\r
+ * Vector row_vec;\r
+ *\r
+ * // inserted a duplicate\r
+ * if ((duplicate_value instanceof vector))\r
+ * {\r
+ * row_vec = (Vector) duplicate_value;\r
+ * }\r
+ * else\r
+ * {\r
+ * // allocate vector to hold duplicates\r
+ * row_vec = new Vector(2);\r
+ *\r
+ * // insert original row into vector\r
+ * row_vec.addElement(duplicate_value);\r
+ *\r
+ * // put the vector as the data rather than the row\r
+ * hash_table.put(row[key_column_number], row_vec);\r
+ * }\r
+ * \r
+ * // insert new row into vector\r
+ * row_vec.addElement(row);\r
+ * }\r
+ * }\r
+ * <p>\r
+ * The columns of the row will be the standard columns returned as\r
+ * part of a scan, as described by the validColumns - see openScan for\r
+ * description.\r
+ * RESOLVE - is this ok? or should I hard code somehow the row to\r
+ * be the first column and the row location?\r
+ * <p>\r
+ * Currently it is only possible to hash on the first column in the\r
+ * conglomerate, in the future we may change the interface to allow\r
+ * hashing either on a different column or maybe on a combination of\r
+ * columns.\r
+ * <p>\r
+ * No overflow to external storage is provided, so calling this routine\r
+ * on a 1 gigabyte conglomerate will incur at least 1 gigabyte of memory\r
+ * (probably failing with a java out of memory condition). If this\r
+ * routine gets an out of memory condition, or if "max_rowcnt" is \r
+ * exceeded then then the routine will give up, empty the Hashtable, \r
+ * and return "false."\r
+ * <p>\r
+ * On exit from this routine, whether the fetchSet() succeeded or not\r
+ * the scan is complete, it is positioned just the same as if the scan\r
+ * had been drained by calling "next()" until it returns false (ie. \r
+ * fetchNext() and next() calls will return false). \r
+ * reopenScan() can be called to restart the scan.\r
+ * <p>\r
+ *\r
+ * RESOLVE - until we get row counts what should we do for sizing the\r
+ * the size, capasity, and load factor of the hash table.\r
+ * For now it is up to the caller to create the Hashtable,\r
+ * Access does not reset any parameters.\r
+ * <p>\r
+ * RESOLVE - I am not sure if access should be in charge of allocating\r
+ * the new row objects. I know that I can do this in the\r
+ * case of btree's, but I don't think I can do this in heaps.\r
+ * Maybe this is solved by work to be done on the sort \r
+ * interface.\r
+ *\r
+ *\r
+ * @param max_rowcnt The maximum number of rows to insert into the \r
+ * Hash table. Pass in -1 if there is no maximum.\r
+ * @param key_column_numbers The column numbers of the columns in the\r
+ * scan result row to be the key to the Hashtable.\r
+ * "0" is the first column in the scan result\r
+ * row (which may be different than the first\r
+ * column in the row in the table of the scan).\r
+ * @param hash_table The java HashTable to load into.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public void fetchSet(\r
+ long max_rowcnt,\r
+ int[] key_column_numbers,\r
+ BackingStoreHashtable hash_table)\r
+ throws StandardException\r
+ {\r
+ // System.out.println("fetchSet");\r
+ \r
+ fetchRows(\r
+ scan_position,\r
+ (DataValueDescriptor[][]) null,\r
+ (RowLocation[]) null,\r
+ (BackingStoreHashtable) hash_table,\r
+ max_rowcnt,\r
+ key_column_numbers);\r
+\r
+ return;\r
+ }\r
+\r
+\r
+ /**\r
+ Reposition the current scan. This call is semantically the same as if\r
+ the current scan had been closed and a openScan() had been called instead.\r
+ The scan is reopened with against the same conglomerate, and the scan\r
+ is reopened with the same "hold" and "forUpdate" parameters passed in\r
+ the original openScan. The previous template row continues to be used.\r
+\r
+ @param startKeyValue An indexable row which holds a\r
+ (partial) key value which, in combination with the\r
+ startSearchOperator, defines the starting position of\r
+ the scan. If null, the starting position of the scan\r
+ is the first row of the conglomerate.\r
+\r
+ @param startSearchOperator an operator which defines\r
+ how the startKeyValue is to be searched for. If\r
+ startSearchOperation is ScanController.GE, the scan starts on\r
+ the first row which is greater than or equal to the\r
+ startKeyValue. If startSearchOperation is ScanController.GT,\r
+ the scan starts on the first row whose key is greater than\r
+ startKeyValue. The startSearchOperation parameter is\r
+ ignored if the startKeyValue parameter is null.\r
+\r
+ @param qualifier An array of qualifiers which, applied\r
+ to each key, restrict the rows returned by the scan. Rows\r
+ for which any one of the qualifiers returns false are not\r
+ returned by the scan. If null, all rows are returned.\r
+\r
+ @param stopKeyValue An indexable row which holds a\r
+ (partial) key value which, in combination with the\r
+ stopSearchOperator, defines the ending position of\r
+ the scan. If null, the ending position of the scan\r
+ is the last row of the conglomerate.\r
+\r
+ @param stopSearchOperator an operator which defines\r
+ how the stopKeyValue is used to determine the scan stopping\r
+ position. If stopSearchOperation is ScanController.GE, the scan\r
+ stops just before the first row which is greater than or\r
+ equal to the stopKeyValue. If stopSearchOperation is\r
+ ScanController.GT, the scan stops just before the first row whose\r
+ key is greater than startKeyValue. The stopSearchOperation\r
+ parameter is ignored if the stopKeyValue parameter is null.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public final void reopenScan(\r
+ DataValueDescriptor[] startKeyValue,\r
+ int startSearchOperator,\r
+ Qualifier qualifier[][],\r
+ DataValueDescriptor[] stopKeyValue,\r
+ int stopSearchOperator)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!init_hold)\r
+ SanityManager.ASSERT(this.container != null,\r
+ "BTreeScan.reopenScan() called on non-held closed scan.");\r
+\r
+ // should only be called by clients outside of store, so should\r
+ // not be possible for a latch to held.\r
+ SanityManager.ASSERT(scan_position.current_leaf == null);\r
+ }\r
+\r
+ // call unlockScanRecordAfterRead() before setting the scan back\r
+ // to init state, so that we release the last lock if necessary (ie.\r
+ // for read committed).\r
+ //\r
+ \r
+ if (scan_position.current_rh != null)\r
+ {\r
+ // reposition to get record handle if we don't have it.\r
+\r
+ if (!reposition(scan_position, false))\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "can not fail while holding update row lock.");\r
+ }\r
+ }\r
+\r
+ this.getLockingPolicy().unlockScanRecordAfterRead(\r
+ scan_position, init_forUpdate);\r
+\r
+ scan_position.current_rh = null;\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+\r
+\r
+ // Need to do this unlock in any case, until lock manager provides\r
+ // a way to release locks associated with a compatibility space. This\r
+ // scan lock is special, as it is a lock on the btree container rather\r
+ // than the heap container. The open container on the btree actually\r
+ // has a null locking policy so the close of that container does not\r
+ // release this lock, need to explicitly unlock it here or when the\r
+ // scan is closed as part of the abort the lock will not be released.\r
+ if (scan_position.current_scan_pageno != 0)\r
+ {\r
+ this.getLockingPolicy().unlockScan(\r
+ scan_position.current_scan_pageno);\r
+ scan_position.current_scan_pageno = 0;\r
+ }\r
+\r
+ scan_position.current_slot = Page.INVALID_SLOT_NUMBER;\r
+ scan_position.current_rh = null;\r
+ scan_position.current_positionKey = null;\r
+\r
+ initScanParams(\r
+ startKeyValue, startSearchOperator, \r
+ qualifier, stopKeyValue, stopSearchOperator);\r
+\r
+ if (!init_hold)\r
+ this.scan_state = SCAN_INIT;\r
+ else\r
+ this.scan_state = \r
+ (this.container != null ? SCAN_INIT : SCAN_HOLD_INIT);\r
+ }\r
+\r
+ /**\r
+ Reposition the current scan. This call is semantically the same as if\r
+ the current scan had been closed and a openScan() had been called instead.\r
+ The scan is reopened against the same conglomerate, and the scan\r
+ is reopened with the same "scan column list", "hold" and "forUpdate"\r
+ parameters passed in the original openScan. \r
+ <p>\r
+ The statistics gathered by the scan are not reset to 0 by a reopenScan(),\r
+ rather they continue to accumulate.\r
+ <p>\r
+ Note that this operation is currently only supported on Heap conglomerates.\r
+ Also note that order of rows within are heap are not guaranteed, so for\r
+ instance positioning at a RowLocation in the "middle" of a heap, then\r
+ inserting more data, then continuing the scan is not guaranteed to see\r
+ the new rows - they may be put in the "beginning" of the heap.\r
+\r
+ @param startRowLocation An existing RowLocation within the conglomerate,\r
+ at which to position the start of the scan. The scan will begin at this\r
+ location and continue forward until the end of the conglomerate. \r
+ Positioning at a non-existent RowLocation (ie. an invalid one or one that\r
+ had been deleted), will result in an exception being thrown when the \r
+ first next operation is attempted.\r
+\r
+ @param qualifier An array of qualifiers which, applied\r
+ to each key, restrict the rows returned by the scan. Rows\r
+ for which any one of the qualifiers returns false are not\r
+ returned by the scan. If null, all rows are returned.\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public void reopenScanByRowLocation(\r
+ RowLocation startRowLocation,\r
+ Qualifier qualifier[][])\r
+ throws StandardException\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE);\r
+ }\r
+\r
+ /*\r
+ ** Methods of ScanController, which are not supported by btree.\r
+ */\r
+\r
+ /**\r
+ Fetch the location of the current position in the scan.\r
+ @see ScanController#fetchLocation\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public void fetchLocation(RowLocation templateLocation)\r
+ throws StandardException\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE);\r
+ }\r
+\r
+ /**\r
+ Return a row location object of the correct type to be\r
+ used in calls to fetchLocation.\r
+ @see org.apache.derby.iapi.store.access.GenericScanController#newRowLocationTemplate\r
+\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public RowLocation newRowLocationTemplate()\r
+ throws StandardException\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE);\r
+ }\r
+\r
+ /**\r
+ Replace the entire row at the current position of the scan.\r
+\r
+ Unimplemented interface by btree, will throw an exception.\r
+\r
+ @see ScanController#replace\r
+ @exception StandardException Standard exception policy.\r
+ **/\r
+ public boolean replace(DataValueDescriptor[] row, FormatableBitSet validColumns)\r
+ throws StandardException\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.BTREE_UNIMPLEMENTED_FEATURE);\r
+ }\r
+\r
+ /*\r
+ ** Methods of ScanManager\r
+ */\r
+\r
+\r
+ /**\r
+ Close the scan, a commit or abort is about to happen.\r
+ **/\r
+ public boolean closeForEndTransaction(boolean closeHeldScan)\r
+ throws StandardException\r
+ {\r
+ if (!init_hold || closeHeldScan)\r
+ {\r
+ // Scan is closed, make sure no access to any state variables\r
+ positionAtDoneScan(scan_position);\r
+\r
+ super.close();\r
+\r
+ // null out so that these object's can get GC'd earlier.\r
+ this.init_rawtran = null;\r
+ this.init_template = null;\r
+ this.init_startKeyValue = null;\r
+ this.init_qualifier = null;\r
+ this.init_stopKeyValue = null;\r
+\r
+ this.getXactMgr().closeMe(this);\r
+\r
+ return(true);\r
+ }\r
+ else\r
+ {\r
+\r
+ if (this.scan_state == SCAN_INPROGRESS)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(scan_position != null);\r
+ }\r
+\r
+ if (scan_position.current_positionKey == null)\r
+ {\r
+ // save position of scan by key rather than location so \r
+ // that we can recover if the page with the position \r
+ // disappears while we don't have a scan lock.\r
+\r
+ savePosition();\r
+ }\r
+ this.scan_state = SCAN_HOLD_INPROGRESS;\r
+ }\r
+ else if (this.scan_state == SCAN_INIT)\r
+ {\r
+ this.scan_state = SCAN_HOLD_INIT;\r
+ }\r
+\r
+ super.close();\r
+\r
+ return(false);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Do work necessary to maintain the current position in the scan.\r
+ * <p>\r
+ * Save the current position of the scan as a key.\r
+ * Do whatever is necessary to maintain the current position of the scan.\r
+ * For some conglomerates this may be a no-op.\r
+ *\r
+ * <p>\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private void savePosition()\r
+ throws StandardException\r
+ {\r
+ if (this.scan_state == SCAN_INPROGRESS)\r
+ {\r
+ // Either current_rh or positionKey is valid - the other is null.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ (scan_position.current_rh == null) == \r
+ (scan_position.current_positionKey != null));\r
+ }\r
+\r
+ try\r
+ {\r
+ if (scan_position.current_rh != null)\r
+ {\r
+ // if scan position is not saved by key, then make it so.\r
+\r
+ // must reposition to get the page latched.\r
+\r
+ if (reposition(scan_position, false))\r
+ {\r
+ scan_position.current_positionKey = \r
+ runtime_mem.get_row_for_export(getRawTran());\r
+\r
+\r
+ Page page = scan_position.current_leaf.getPage();\r
+\r
+\r
+ RecordHandle rh =\r
+ page.fetchFromSlot(\r
+ (RecordHandle) null,\r
+ page.getSlotNumber(scan_position.current_rh), \r
+ scan_position.current_positionKey, \r
+ (FetchDescriptor) null,\r
+ true);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(rh != null);\r
+ }\r
+\r
+ scan_position.current_rh = null;\r
+ scan_position.current_slot = Page.INVALID_SLOT_NUMBER;\r
+\r
+ // release scan lock now that the row is saved away.\r
+\r
+ if (scan_position.current_scan_pageno != 0)\r
+ {\r
+ this.getLockingPolicy().unlockScan(\r
+ scan_position.current_scan_pageno);\r
+ scan_position.current_scan_pageno = 0;\r
+ }\r
+\r
+ }\r
+ else\r
+ {\r
+ // this should never happen as we hold the scan lock\r
+ // on the page while maintaining the position by \r
+ // recordhandle - reposition should always work in this\r
+ // case.\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT(\r
+ "Must always be able to reposition.");\r
+ }\r
+ }\r
+\r
+ }\r
+ finally\r
+ {\r
+\r
+ if (scan_position.current_leaf != null)\r
+ {\r
+ // release latch on page\r
+ scan_position.current_leaf.release();\r
+ scan_position.current_leaf = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Do work necessary to maintain the current position in the scan.\r
+ * <p>\r
+ * The latched page in the conglomerate "congomid" is changing, do\r
+ * whatever is necessary to maintain the current position of the scan.\r
+ * For some conglomerates this may be a no-op.\r
+ * <p>\r
+ *\r
+ * @param conglom Conglomerate object of the conglomerate being changed.\r
+ * @param page Page in the conglomerate being changed.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public void savePosition(Conglomerate conglom, Page page)\r
+ throws StandardException\r
+ {\r
+ // page should be latched by split. This scan is assuming that latch\r
+ // and reading off it's key from the page under the split's latch.\r
+ // A lock should have already been gotten on this row.\r
+\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(page.isLatched());\r
+ }\r
+\r
+ /*\r
+ System.out.println(\r
+ "Saving position in btree at top: " +\r
+ " this.conglomerate = " + this.conglomerate +\r
+ " this.scan_state = " + this.scan_state);\r
+ SanityManager.DEBUG_PRINT("savePosition()", \r
+ "Saving position in btree at top: " +\r
+ " this.conglomerate = " + this.conglomerate +\r
+ " this.scan_state = " + this.scan_state);\r
+ */\r
+\r
+\r
+ if ((this.getConglomerate() == conglom) &&\r
+ (this.scan_state == SCAN_INPROGRESS))\r
+ {\r
+ // Either current_rh or positionKey is valid - the other is null.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ (scan_position.current_rh == null) == \r
+ (scan_position.current_positionKey != null));\r
+ }\r
+\r
+ /*\r
+ SanityManager.DEBUG_PRINT("savePosition()", \r
+ "Saving position in btree: " +\r
+ ";current_scan_pageno = " + this.current_scan_pageno +\r
+ "this.current_rh = " + this.current_rh +\r
+ ";page.getPageNumber() = " + page.getPageNumber() +\r
+ ((this.current_rh != null) ?\r
+ (";this.current_rh.getPageNumber() = " +\r
+ this.current_rh.getPageNumber()) : ""));\r
+ */\r
+\r
+ if (scan_position.current_rh != null &&\r
+ page.getPageNumber() == \r
+ scan_position.current_rh.getPageNumber())\r
+ {\r
+ scan_position.current_positionKey = \r
+ runtime_mem.get_row_for_export(getRawTran());\r
+\r
+ RecordHandle rh =\r
+ page.fetchFromSlot(\r
+ (RecordHandle) null,\r
+ page.getSlotNumber(scan_position.current_rh), \r
+ scan_position.current_positionKey, \r
+ (FetchDescriptor) null,\r
+ true);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(rh != null);\r
+ }\r
+\r
+ scan_position.current_rh = null;\r
+ scan_position.current_slot = Page.INVALID_SLOT_NUMBER;\r
+\r
+ // release the scan lock now that we have saved away the row.\r
+\r
+ if (scan_position.current_scan_pageno != 0)\r
+ {\r
+ this.getLockingPolicy().unlockScan(\r
+ scan_position.current_scan_pageno);\r
+ scan_position.current_scan_pageno = 0;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ public RecordHandle getCurrentRecordHandleForDebugging()\r
+ {\r
+ return(scan_position.current_rh);\r
+ }\r
+\r
+ /*\r
+ ** Standard toString() method. Prints out current position in scan.\r
+ */\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ String string =\r
+ "\n\tbtree = " + this.getConglomerate() +\r
+ "\n\tscan direction = " +\r
+ (this instanceof BTreeForwardScan ? "forward" :\r
+ (this instanceof BTreeMaxScan ? "backward" : \r
+ "illegal")) +\r
+ "\n\t(scan_state:" +\r
+ (this.scan_state == SCAN_INIT ? "SCAN_INIT" :\r
+ this.scan_state == SCAN_INPROGRESS ? "SCAN_INPROGRESS" :\r
+ this.scan_state == SCAN_DONE ? "SCAN_DONE" :\r
+ this.scan_state == SCAN_HOLD_INIT ? "SCAN_HOLD_INIT" :\r
+ this.scan_state == SCAN_HOLD_INPROGRESS ? "SCAN_HOLD_INPROGRESS" :\r
+ "BAD_SCAN_STATE") +\r
+ "\n\trh:" + scan_position.current_rh +\r
+ "\n\tkey:" + scan_position.current_positionKey + ")" +\r
+ "\n\tinit_rawtran = " + init_rawtran +\r
+ "\n\tinit_hold = " + init_hold +\r
+ "\n\tinit_forUpdate = " + init_forUpdate +\r
+ "\n\tinit_useUpdateLocks = " + init_useUpdateLocks +\r
+ "\n\tinit_scanColumnList = " + init_scanColumnList +\r
+ "\n\tinit_scanColumnList.size() = "+ (\r
+ (init_scanColumnList != null ? \r
+ init_scanColumnList.size() : 0)) +\r
+ "\n\tinit_template = " + \r
+ RowUtil.toString(init_template) +\r
+ "\n\tinit_startKeyValue = " + \r
+ RowUtil.toString(init_startKeyValue) +\r
+ "\n\tinit_startSearchOperator = " + \r
+ (init_startSearchOperator == ScanController.GE ? "GE" : \r
+ (init_startSearchOperator == ScanController.GT ? "GT" : \r
+ Integer.toString(init_startSearchOperator))) +\r
+ "\n\tinit_qualifier[] = " + init_qualifier +\r
+ "\n\tinit_stopKeyValue = " + \r
+ RowUtil.toString(init_stopKeyValue) +\r
+ "\n\tinit_stopSearchOperator = " + \r
+ (init_stopSearchOperator == ScanController.GE ? "GE" : \r
+ (init_stopSearchOperator == ScanController.GT ? "GT" : \r
+ Integer.toString(init_stopSearchOperator))) +\r
+ "\n\tstat_numpages_visited = " + \r
+ stat_numpages_visited +\r
+ "\n\tstat_numrows_visited = " + \r
+ stat_numrows_visited +\r
+ "\n\tstat_numrows_qualified = " + \r
+ stat_numrows_qualified +\r
+ "\n\tstat_numdeleted_rows_visited = " + \r
+ stat_numdeleted_rows_visited ;\r
+\r
+ return(string);\r
+ }\r
+ else\r
+ {\r
+ return(null);\r
+ }\r
+ }\r
+}\r