Source code: org/apache/derby/impl/store/access/conglomerate/GenericScanController.java
1 /*
2
3 Derby - Class org.apache.derby.impl.store.access.conglomerate.GenericScanController
4
5 Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 */
20
21 package org.apache.derby.impl.store.access.conglomerate;
22
23 import org.apache.derby.iapi.reference.SQLState;
24
25 import org.apache.derby.iapi.services.sanity.SanityManager;
26
27 import org.apache.derby.iapi.error.StandardException;
28
29 import org.apache.derby.iapi.store.access.conglomerate.Conglomerate;
30 import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo;
31 import org.apache.derby.iapi.store.access.conglomerate.ScanManager;
32 import org.apache.derby.iapi.store.access.conglomerate.TransactionManager;
33
34 import org.apache.derby.iapi.store.access.ConglomerateController;
35 import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
36 import org.apache.derby.iapi.store.access.Qualifier;
37 import org.apache.derby.iapi.store.access.RowUtil;
38 import org.apache.derby.iapi.store.access.ScanController;
39 import org.apache.derby.iapi.store.access.ScanInfo;
40 import org.apache.derby.iapi.store.access.SpaceInfo;
41
42 import org.apache.derby.iapi.store.raw.ContainerHandle;
43 import org.apache.derby.iapi.store.raw.FetchDescriptor;
44 import org.apache.derby.iapi.store.raw.Page;
45 import org.apache.derby.iapi.store.raw.RecordHandle;
46 import org.apache.derby.iapi.store.raw.Transaction;
47
48 import org.apache.derby.iapi.store.access.Qualifier;
49
50 import org.apache.derby.iapi.types.DataValueDescriptor;
51
52 import org.apache.derby.iapi.types.Orderable;
53 import org.apache.derby.iapi.types.RowLocation;
54
55
56 import org.apache.derby.iapi.store.access.BackingStoreHashtable;
57 import org.apache.derby.iapi.services.io.FormatableBitSet;
58
59 import java.util.Properties;
60
61
62 /**
63 Generic class implementing shared ScanController methods.
64
65 Logically a scancontroller is used to scan a set of rows that meet some
66 specified qualification. Rows that meet the qualification may be operated
67 upon by the scan to fetch, delete, or replace. The ScanController also
68 supports the notion or "repositioning" the scan, which simply resets the
69 beginning of the scan to a new place, and allows the user to continue from
70 there.
71
72 This class attempts to abstract out some of the parts of the scan such that
73 maybe multiple access methods can share code, even if they perform parts of
74 the scan wildly differently. Here is how the scan has been broken apart:
75
76 scan_position - this variable holds the current scan position, it may be
77 extended
78 to provide more information if necessary.
79
80 scan_state - a scan has 3 possible states:
81 SCAN_INIT, SCAN_INPROGRESS, SCAN_DONE
82
83 positionAtInitScan()
84 - This routine is called to move the scan to the SCAN_INIT state.
85 It is used both for initialization of the ScanController and
86 by reopenScan().
87
88 positionAtStartForForwardScan()
89 - This routine is called to move the scan from SCAN_INIT to
90 SCAN_INPROGRESS. Upon return from this routine it is expected
91 that scan_position is set such that calling the generic
92 scan loop will reach the first row of the scan. Note that this
93 usually means setting the scan_postion to one before the 1st
94 row to be returned.
95
96 fetchRows() - This routine is the meat of the scan, it moves the scan to the
97 next row, applies necessary qualifiers, and handles group or
98 non-group operations. It moves through rows on a page in
99 order and then moves to the "next" page.
100
101 positionAtNextPage()
102 - This routine handles moving the scan from the current
103 scan_position to the next page.
104
105 positionAtDoneScan()
106 - Handle all cleanup associated with moving the scan state from
107 SCAN_INPROGRESS to SCAN_DONE. This may include releasing locks,
108 and setting the state of the scan. This does not close the
109 scan, it allows for a reopenScan() to be called.
110 **/
111
112 public abstract class GenericScanController
113 extends GenericController implements ScanManager
114 {
115
116 /**************************************************************************
117 * Constants of the class
118 **************************************************************************
119 */
120
121 /*
122 * There are 5 states a scan can be in.
123 * SCAN_INIT - A scan has started but no positioning has been done.
124 * The scan will be positioned when the first next() call
125 * has been made. None of the positioning state variables
126 * are valid in this state.
127 * SCAN_INPROGRESS -
128 * A scan is in this state after the first next() call.
129 * On exit from any GenericScanController method, while in
130 * this state,
131 * the scan "points" at a row which qualifies for the
132 * scan. While not maintaining latches on a page the
133 * current position of the scan is either kept by record
134 * handle or key. To tell which use the following:
135 * if (record key == null)
136 * record handle has current position
137 * else
138 * record key has current position
139 *
140 * SCAN_DONE - Once the end of the table or the stop condition is met
141 * then the scan is placed in this state. Only valid
142 * ScanController method at this point is close().
143 *
144 * SCAN_HOLD_INIT -
145 * The scan has been opened and held open across a commit,
146 * at the last commit the state was SCAN_INIT.
147 * The scan has never progressed from the SCAN_INIT state
148 * during a transaction. When a next is done the state
149 * will either progress to SCAN_INPROGRESS or SCAN_DONE.
150 *
151 * SCAN_HOLD_INPROGRESS -
152 * The scan has been opened and held open across a commit,
153 * at the last commit the state was in SCAN_INPROGRESS.
154 * The transaction which opened the scan has committed,
155 * but the scan was opened with the "hold" option true.
156 * At commit the locks were released and the "current"
157 * position is remembered. In this state only two calls
158 * are valid, either next() or close(). When next() is
159 * called the scan is reopened, the underlying container
160 * is opened thus associating all new locks with the current
161 * transaction, and the scan continues at the "next" row.
162 */
163 public static final int SCAN_INIT = 1;
164 public static final int SCAN_INPROGRESS = 2;
165 public static final int SCAN_DONE = 3;
166 public static final int SCAN_HOLD_INIT = 4;
167 public static final int SCAN_HOLD_INPROGRESS = 5;
168
169 /**************************************************************************
170 * Fields of the class
171 **************************************************************************
172 */
173
174 /**
175 * The following group of fields are all basic input parameters which are
176 * provided by the calling code when doing a scan.
177 * These are just saved values from what was initially input.
178 **/
179 private FormatableBitSet init_scanColumnList;
180 private DataValueDescriptor[] init_startKeyValue;
181 private int init_startSearchOperator;
182 private Qualifier[][] init_qualifier;
183 private DataValueDescriptor[] init_stopKeyValue;
184 private int init_stopSearchOperator;
185
186 private FetchDescriptor init_fetchDesc;
187
188 /**
189 * Delay positioning the table at the start position until the first
190 * next() call.
191 */
192 private int scan_state;
193
194
195 /**
196 * The position for the current scan. The can be maintained in any
197 * of the following ways:
198 * record handle - scan_position.current_rh:
199 * The scan maintains it's position using the record handle while
200 * it does not have a latch on the page, which is the case anytime
201 * control leaves access. The access method must take appropriate
202 * steps to make sure the record handle will still be valid when
203 * the scan needs to reposition using the record handle.
204 * slot number - scan_position.current_slot:
205 * While the scan has a latch on the page the scan is positioned
206 * using the slot number as the order of the rows cannot change
207 * while the latch is held (unless the holder of the latch causes
208 * them to move).
209 * page number - (RESOLVE - TODO)
210 * Sometimes it would be interesting to position a scan "between"
211 * pages, such that the next time the scan starts is starts at
212 * the next page. This would allow us to efficiently do group
213 * scans returning page at atime results.
214 * NOT IMPLEMENTED CURRENTLY.
215 **/
216 protected RowPosition scan_position;
217
218 /**
219 * Performance counters ...
220 */
221 protected int stat_numpages_visited = 0;
222 protected int stat_numrows_visited = 0;
223 protected int stat_numrows_qualified = 0;
224
225 /**************************************************************************
226 * Constructors for This class:
227 **************************************************************************
228 */
229
230 /**************************************************************************
231 * Private methods of This class:
232 **************************************************************************
233 */
234
235 private final void repositionScanForUpateOper()
236 throws StandardException
237 {
238 if (scan_state != SCAN_INPROGRESS)
239 throw StandardException.newException(
240 SQLState.AM_SCAN_NOT_POSITIONED);
241
242
243 if (!open_conglom.latchPage(scan_position))
244 {
245 throw StandardException.newException(
246 SQLState.AM_RECORD_NOT_FOUND,
247 open_conglom.getContainer().getId(),
248 new Long(scan_position.current_rh.getId()));
249 }
250
251 if (open_conglom.isUseUpdateLocks())
252 {
253 // we only have an U lock at this point which was acquired when the
254 // scan positioned on the row, need to request an
255 // X lock before we can actually perform the delete
256
257 open_conglom.lockPositionForWrite(
258 scan_position, false /* not insert */, true);
259 }
260 }
261
262
263 /**************************************************************************
264 * Protected methods implementing mechanics of scanning rows:
265 *
266 * positionAtInitScan() - move scan state to SCAN_INIT
267 * positionAtStartForForwardScan() - SCAN_INIT -> SCAN_INPROGRESS
268 * positionAtResumeScan() - reposition after losing scan latch
269 * fetchRows() - move scan while in SCAN_INPROGRESS
270 * positionAtNextPage() - move page while in SCAN_INPROGRESS
271 * positionAtDoneScan() - SCAN_INPROGRESS -> SCAN_DONE
272 *
273 **************************************************************************
274 */
275
276 /**
277 * Move scan to the the SCAN_INIT state.
278 * <p>
279 * This routine is called to move the scan to the SCAN_INIT state.
280 * It is used both for initialization of the ScanController and
281 * by reopenScan().
282 **/
283 protected void positionAtInitScan(
284 DataValueDescriptor[] startKeyValue,
285 int startSearchOperator,
286 Qualifier qualifier[][],
287 DataValueDescriptor[] stopKeyValue,
288 int stopSearchOperator,
289 RowPosition pos)
290 throws StandardException
291 {
292 // startKeyValue init.
293 this.init_startKeyValue = startKeyValue;
294 if (RowUtil.isRowEmpty(this.init_startKeyValue, (FormatableBitSet) null))
295 this.init_startKeyValue = null;
296
297 // startSearchOperator init.
298 this.init_startSearchOperator = startSearchOperator;
299
300 // qualifier init.
301 if ((qualifier != null) && (qualifier .length == 0))
302 qualifier = null;
303 this.init_qualifier = qualifier;
304
305 // TODO (mikem) - this could be more efficient, by writing
306 // code to figure out length of row, but scratch row is cached
307 // so allocating it here is probably not that bad.
308 init_fetchDesc =
309 new FetchDescriptor(
310 (open_conglom.getRuntimeMem().get_scratch_row()).length,
311 init_scanColumnList,
312 init_qualifier);
313
314 // stopKeyValue init.
315 this.init_stopKeyValue = stopKeyValue;
316 if (RowUtil.isRowEmpty(this.init_stopKeyValue, (FormatableBitSet) null))
317 this.init_stopKeyValue = null;
318
319 // stopSearchOperator init.
320 this.init_stopSearchOperator = stopSearchOperator;
321
322 // reset the "current" position to starting condition.
323 pos.init();
324
325
326 // Verify that all columns in start key value, stop key value, and
327 // qualifiers are present in the list of columns described by the
328 // scanColumnList.
329 if (SanityManager.DEBUG)
330 {
331 if (init_scanColumnList != null)
332 {
333 // verify that all columns specified in qualifiers, start
334 // and stop positions are specified in the scanColumnList.
335
336 FormatableBitSet required_cols;
337
338 if (qualifier != null)
339 required_cols = RowUtil.getQualifierBitSet(qualifier);
340 else
341 required_cols = new FormatableBitSet(0);
342
343 // add in start columns
344 if (this.init_startKeyValue != null)
345 {
346 required_cols.grow(this.init_startKeyValue.length);
347 for (int i = 0; i < this.init_startKeyValue.length; i++)
348 required_cols.set(i);
349 }
350
351 if (this.init_stopKeyValue != null)
352 {
353 required_cols.grow(this.init_stopKeyValue.length);
354 for (int i = 0; i < this.init_stopKeyValue.length; i++)
355 required_cols.set(i);
356 }
357
358 FormatableBitSet required_cols_and_scan_list =
359 (FormatableBitSet) required_cols.clone();
360
361 required_cols_and_scan_list.and(init_scanColumnList);
362
363 // FormatableBitSet equals requires the two FormatableBitSets to be of same
364 // length.
365 required_cols.grow(init_scanColumnList.size());
366
367 if (!required_cols_and_scan_list.equals(required_cols))
368 {
369 SanityManager.THROWASSERT(
370 "Some column specified in a Btree " +
371 " qualifier/start/stop list is " +
372 "not represented in the scanColumnList." +
373 "\n:required_cols_and_scan_list = " +
374 required_cols_and_scan_list +
375 "\n;required_cols = " + required_cols +
376 "\n;init_scanColumnList = " + init_scanColumnList);
377 }
378 }
379 }
380
381 // Scan is fully initialized and ready to go.
382 scan_state = SCAN_INIT;
383 }
384
385
386 /**
387 * Reposition the scan upon entering the fetchRows loop.
388 * <p>
389 * Called upon entering fetchRows() while in the SCAN_INPROGRESS state.
390 * Do work necessary to look at rows in the current page of the scan.
391 * <p>
392 * The default implementation uses a record handle to maintain a scan
393 * position. It will get the latch again on the current
394 * scan position and set the slot to the current record handle.
395 *
396 * @exception StandardException Standard exception policy.
397 **/
398 protected void positionAtResumeScan(
399 RowPosition pos)
400 throws StandardException
401 {
402 if (SanityManager.DEBUG)
403 {
404 SanityManager.ASSERT(
405 scan_position.current_rh != null, this.toString());
406 }
407
408 // reposition the scan at the row just before the next one to return.
409 // This routine handles the mess of repositioning if the row or the
410 // page has disappeared. This can happen if a lock was not held on the
411 // row while not holding the latch.
412 open_conglom.latchPageAndRepositionScan(scan_position);
413 }
414
415 /**
416 * Move the scan from SCAN_INIT to SCAN_INPROGRESS.
417 * <p>
418 * This routine is called to move the scan from SCAN_INIT to
419 * SCAN_INPROGRESS. Upon return from this routine it is expected
420 * that scan_position is set such that calling the generic
421 * scan loop will reach the first row of the scan. Note that this
422 * usually means setting the scan_postion to one before the 1st
423 * row to be returned.
424 * <p>
425 *
426 * @exception StandardException Standard exception policy.
427 **/
428 protected void positionAtStartForForwardScan(
429 RowPosition pos)
430 throws StandardException
431 {
432 if (pos.current_rh == null)
433 {
434 // 1st positioning of scan (delayed from openScan).
435 pos.current_page =
436 open_conglom.getContainer().getFirstPage();
437
438 if (SanityManager.DEBUG)
439 {
440 SanityManager.ASSERT(
441 pos.current_page.getPageNumber() ==
442 ContainerHandle.FIRST_PAGE_NUMBER);
443
444 if (pos.current_page.recordCount() < 1)
445 SanityManager.THROWASSERT(
446 "record count = " + pos.current_page.recordCount());
447 }
448
449 // set up for scan to continue at beginning of first page just
450 // after first first control row on first page.
451 pos.current_slot = Page.FIRST_SLOT_NUMBER;
452 }
453 else
454 {
455 // 1st positioning of scan following a reopenScanByRowLocation
456
457 // reposition the scan at the row just before the next one to
458 // return. This routine handles the mess of repositioning if the
459 // row or the page has disappeared. This can happen if a lock was
460 // not held on the row while not holding the latch.
461 open_conglom.latchPageAndRepositionScan(pos);
462
463 // set up for scan to at the specified record handle (position one
464 // before it so that the loop increment and find it).
465 pos.current_slot -= 1;
466 }
467
468 pos.current_rh = null;
469 this.stat_numpages_visited = 1;
470 this.scan_state = SCAN_INPROGRESS;
471 }
472
473 /**
474 * Position scan to slot before first slot on next page.
475 * <p>
476 * @exception StandardException Standard exception policy.
477 **/
478 protected void positionAtNextPage(
479 RowPosition pos)
480 throws StandardException
481 {
482 // The current_page can become null, in a rare multi-user case, where
483 // all pages in the heap are deallocated, in the middle of the scan
484 // loop, when no latches are held, and the scan is waiting on a lock.
485 // In this case the lockPositionForRead code, has nowhere good to
486 // position the scan, so it just sets the page to null and returns.
487 if (pos.current_page != null)
488 {
489 // save current page number.
490 long pageid = pos.current_page.getPageNumber();
491
492 // unlatch old page.
493 pos.unlatch();
494
495 // latch page after current page number.
496 pos.current_page =
497 open_conglom.getContainer().getNextPage(pageid);
498
499 // set up for scan to continue at beginning of this new page.
500 pos.current_slot = Page.FIRST_SLOT_NUMBER - 1;
501 }
502 }
503
504 /**
505 * Do any necessary work to complete the scan.
506 *
507 * @exception StandardException Standard exception policy.
508 **/
509 protected void positionAtDoneScan(
510 RowPosition pos)
511 throws StandardException
512 {
513 // Unlatch current page if any.
514 pos.unlatch();
515
516 // unlock the previous row.
517 if (scan_position.current_rh != null)
518 {
519 open_conglom.unlockPositionAfterRead(scan_position);
520 scan_position.current_rh = null;
521 }
522
523 this.scan_state = SCAN_DONE;
524 }
525
526 public void reopenScanByRowLocation(
527 RowLocation startRowLocation,
528 Qualifier qualifier[][])
529 throws StandardException
530 {
531 throw StandardException.newException(
532 SQLState.BTREE_UNIMPLEMENTED_FEATURE);
533 }
534
535 /**************************************************************************
536 * Protected methods of This class:
537 **************************************************************************
538 */
539
540 /**
541 * Create object which represents the scan position.
542 * <p>
543 * Designed so that extending classes can override and allocate
544 * implementation specific row position's.
545 *
546 * @exception StandardException Standard exception policy.
547 **/
548 protected RowPosition allocateScanPosition()
549 throws StandardException
550 {
551 return(new RowPosition());
552 }
553
554 /**
555 * Fetch the next N rows from the table.
556 * <p>
557 * Utility routine used by both fetchSet() and fetchNextGroup().
558 *
559 * @exception StandardException Standard exception policy.
560 **/
561 protected int fetchRows(
562 DataValueDescriptor[][] row_array,
563 RowLocation[] rowloc_array,
564 BackingStoreHashtable hash_table,
565 long max_rowcnt,
566 int[] key_column_numbers)
567 throws StandardException
568 {
569 int ret_row_count = 0;
570 DataValueDescriptor[] fetch_row = null;
571
572 if (max_rowcnt == -1)
573 max_rowcnt = Long.MAX_VALUE;
574
575 if (SanityManager.DEBUG)
576 {
577 if (row_array != null)
578 {
579 SanityManager.ASSERT(row_array[0] != null,
580 "first array slot in fetchNextGroup() must be non-null.");
581 SanityManager.ASSERT(hash_table == null);
582 }
583 else
584 {
585 SanityManager.ASSERT(hash_table != null);
586 }
587 }
588
589 if (this.scan_state == SCAN_INPROGRESS)
590 {
591 positionAtResumeScan(scan_position);
592 }
593 else if (this.scan_state == SCAN_INIT)
594 {
595 positionAtStartForForwardScan(scan_position);
596
597 }
598 else if (this.scan_state == SCAN_HOLD_INPROGRESS)
599 {
600 open_conglom.reopen();
601
602 if (SanityManager.DEBUG)
603 {
604 SanityManager.ASSERT(
605 scan_position.current_rh != null, this.toString());
606 }
607
608 // reposition the scan at the row just before the next one to
609 // return.
610 // This routine handles the mess of repositioning if the row or
611 // the page has disappeared. This can happen if a lock was not
612 // held on the row while not holding the latch.
613 open_conglom.latchPageAndRepositionScan(scan_position);
614
615 this.scan_state = SCAN_INPROGRESS;
616 }
617 else if (this.scan_state == SCAN_HOLD_INIT)
618 {
619 open_conglom.reopen();
620
621 positionAtStartForForwardScan(scan_position);
622
623 }
624 else
625 {
626 if (SanityManager.DEBUG)
627 SanityManager.ASSERT(this.scan_state == SCAN_DONE);
628
629 return(0);
630 }
631
632 // At this point:
633 // scan_position.current_page is latched.
634 // scan_position.current_slot is the slot on scan_position.current_page
635 // just before the "next" record this routine should process.
636
637 // loop through successive pages and successive slots on those
638 // pages. Stop when either the last page is reached
639 // (scan_position.current_page will be null).
640 // Along the way apply qualifiers to skip rows which don't qualify.
641
642 while (scan_position.current_page != null)
643 {
644 while ((scan_position.current_slot + 1) <
645 scan_position.current_page.recordCount())
646 {
647 // unlock the previous row.
648 if (scan_position.current_rh != null)
649 {
650 open_conglom.unlockPositionAfterRead(scan_position);
651
652 }
653 // Allocate a new row to read the row into.
654 if (fetch_row == null)
655 {
656 if (hash_table == null)
657 {
658 // point at allocated row in array if one exists.
659 if (row_array[ret_row_count] == null)
660 {
661 row_array[ret_row_count] =
662 open_conglom.getRuntimeMem().get_row_for_export();
663 }
664
665 fetch_row = row_array[ret_row_count];
666 }
667 else
668 {
669 fetch_row =
670 open_conglom.getRuntimeMem().get_row_for_export();
671 }
672 }
673
674 // move scan current position forward.
675 scan_position.positionAtNextSlot();
676
677 // Lock the row.
678 boolean lock_granted_while_latch_held =
679 open_conglom.lockPositionForRead(
680 scan_position, (RowPosition) null, true, true);
681
682 if (!lock_granted_while_latch_held)
683 {
684 // if lock could not be granted while holding
685 // latch, then the row may either be on the same page
686 // or it may no longer exist, this implementation does not
687 // handle rows which move to different pages.
688 //
689 // If the row moved on the same page then
690 // lockPositionForRead() will have automatically updated
691 // the scan_postion argument to point to it, and we
692 // wil now have a latch and a lock on that row.
693 //
694 // If the row no longer exists then the
695 // "moveForwardIfRowDisappears" argument makes this routine
696 // find the "next" row in the heap and position on it. If
697 // a valid row exists in the current page to position on,
698 // then lockPositionForRead() will position on it, get
699 // a lock on it, and return with a latch on the page.
700 // Otherwise the routine will return with current_slot == -1
701 // and it is up to this routine to continue the scan as
702 // normal at the top of the loop.
703
704 if (scan_position.current_page == null)
705 {
706 // page has been unlatched and the scan is done, there
707 // are no more pages. getNextPage() has been coded to
708 // handle a null current_page.
709
710 break;
711 }
712 else if (scan_position.current_slot == -1)
713 {
714 // This means that lockPositionForRead() had to
715 // reposition the scan forward to a new page, because
716 // the row the scan was locking was purged, when the
717 // latch was released to wait on the lock. In this
718 // case just jump back to the top of loop and continue
719 // scan.
720
721 if (SanityManager.DEBUG)
722 {
723 SanityManager.ASSERT(
724 scan_position.current_rh == null);
725 }
726
727 continue;
728 }
729 }
730
731 this.stat_numrows_visited++;
732
733 // lockRowAtPosition set pos.current_rh as part of getting lock.
734 if (SanityManager.DEBUG)
735 {
736 SanityManager.ASSERT(scan_position.current_rh != null);
737
738 // make sure current_rh and current_slot are in sync
739 if (scan_position.current_slot !=
740 scan_position.current_page.getSlotNumber(
741 scan_position.current_rh))
742 {
743 SanityManager.THROWASSERT(
744 "current_slot = " + scan_position.current_slot +
745 "current_rh = " + scan_position.current_rh +
746 "current_rh.slot = " +
747 scan_position.current_page.getSlotNumber(
748 scan_position.current_rh));
749 }
750 }
751
752 // fetchFromSlot returns null if row does not qualify.
753
754 scan_position.current_rh_qualified =
755 (scan_position.current_page.fetchFromSlot(
756 scan_position.current_rh,
757 scan_position.current_slot,
758 fetch_row,
759 init_fetchDesc,
760 false) != null);
761
762 if (scan_position.current_rh_qualified)
763 {
764 // qualifying row.
765
766
767 // scan_position.current_rh is save position of scan while
768 // latch is not held. It currently points at the
769 // scan_position.current_slot in search (while latch is
770 // held).
771 if (SanityManager.DEBUG)
772 {
773 // make sure current_rh and current_slot are in sync
774 SanityManager.ASSERT(
775 scan_position.current_slot ==
776 scan_position.current_page.getSlotNumber(
777 scan_position.current_rh));
778 }
779
780 // Found qualifying row. Done fetching rows for the group?
781 ret_row_count++;
782 stat_numrows_qualified++;
783
784
785 if (hash_table == null)
786 {
787 if (rowloc_array != null)
788 {
789 // if requested return the associated row location.
790 setRowLocationArray(
791 rowloc_array, ret_row_count - 1, scan_position);
792 }
793
794 fetch_row = null;
795 }
796 else
797 {
798 if (hash_table.put(false, fetch_row))
799 {
800 // The row was inserted into the hash table so we
801 // need to create a new row next time through.
802 fetch_row = null;
803 }
804 }
805
806 if (max_rowcnt <= ret_row_count)
807 {
808 // exit fetch row loop and return to the client.
809 scan_position.unlatch();
810
811 if (SanityManager.DEBUG)
812 {
813 SanityManager.ASSERT(
814 scan_position.current_rh != null);
815 }
816
817 return(ret_row_count);
818 }
819 }
820 }
821
822 positionAtNextPage(scan_position);
823
824 this.stat_numpages_visited++;
825 }
826
827 // Reached last page of scan.
828 positionAtDoneScan(scan_position);
829
830 // we need to decrement when we stop scan at the end of the table.
831 this.stat_numpages_visited--;
832
833 return(ret_row_count);
834 }
835
836 /**
837 Reposition the current scan. This call is semantically the same as if
838 the current scan had been closed and a openScan() had been called instead.
839 The scan is reopened against the same conglomerate, and the scan
840 is reopened with the same "scan column list", "hold" and "forUpdate"
841 parameters passed in the original openScan.
842 <p>
843 The statistics gathered by the scan are not reset to 0 by a reopenScan(),
844 rather they continue to accumulate.
845 <p>
846 Note that this operation is currently only supported on Heap conglomerates.
847 Also note that order of rows within are heap are not guaranteed, so for
848 instance positioning at a RowLocation in the "middle" of a heap, then
849 inserting more data, then continuing the scan is not guaranteed to see
850 the new rows - they may be put in the "beginning" of the heap.
851
852 @param startRecordHandle An existing RecordHandle within the conglomerate,
853 at which to position the start of the scan. The scan will begin at this
854 location and continue forward until the end of the conglomerate.
855 Positioning at a non-existent RowLocation (ie. an invalid one or one that
856 had been deleted), will result in an exception being thrown when the
857 first next operation is attempted.
858
859 @param qualifier An array of qualifiers which, applied
860 to each key, restrict the rows returned by the scan. Rows
861 for which any one of the qualifiers returns false are not
862 returned by the scan. If null, all rows are returned.
863
864 @exception StandardException Standard exception policy.
865 **/
866 protected void reopenScanByRecordHandle(
867 RecordHandle startRecordHandle,
868 Qualifier qualifier[][])
869 throws StandardException
870 {
871 // initialize scan position parameters at beginning of scan
872 this.scan_state =
873 (!open_conglom.getHold() ? SCAN_INIT : SCAN_HOLD_INIT);
874
875 // position the scan at the row before the given record id, so that
876 // the first "next" starts on the given row.
877 scan_position.current_rh = startRecordHandle;
878 }
879
880 protected void setRowLocationArray(
881 RowLocation[] rowloc_array,
882 int index,
883 RowPosition pos)
884 throws StandardException
885 {
886 throw(StandardException.newException(
887 SQLState.HEAP_UNIMPLEMENTED_FEATURE));
888 }
889
890 /**************************************************************************
891 * abstract protected Methods of This class:
892 **************************************************************************
893 */
894
895 /**************************************************************************
896 * Public Methods of This class:
897 **************************************************************************
898 */
899 public void init(
900 OpenConglomerate open_conglom,
901 FormatableBitSet scanColumnList,
902 DataValueDescriptor[] startKeyValue,
903 int startSearchOperator,
904 Qualifier qualifier[][],
905 DataValueDescriptor[] stopKeyValue,
906 int stopSearchOperator)
907 throws StandardException
908 {
909 super.init(open_conglom);
910
911 // RESOLVE (mikem) - move this into runtime_mem
912 scan_position = allocateScanPosition();
913
914 // remember inputs
915 init_scanColumnList = scanColumnList;
916
917 positionAtInitScan(
918 startKeyValue,
919 startSearchOperator,
920 qualifier,
921 stopKeyValue,
922 stopSearchOperator,
923 scan_position);
924 }
925
926
927 public final int getNumPagesVisited()
928 {
929 return(stat_numpages_visited);
930 }
931 public final int getNumRowsVisited()
932 {
933 return(stat_numrows_visited);
934 }
935 public final int getNumRowsQualified()
936 {
937 return(stat_numrows_qualified);
938 }
939 public final FormatableBitSet getScanColumnList()
940 {
941 return(init_scanColumnList);
942 }
943 public final DataValueDescriptor[] getStartKeyValue()
944 {
945 return(init_startKeyValue);
946 }
947 public final int getStartSearchOperator()
948 {
949 return(init_startSearchOperator);
950 }
951 public final DataValueDescriptor[] getStopKeyValue()
952 {
953 return(init_stopKeyValue);
954 }
955 public final int getStopSearchOperator()
956 {
957 return(init_stopSearchOperator);
958 }
959 public final Qualifier[][] getQualifier()
960 {
961 return(init_qualifier);
962 }
963
964
965 public final int getScanState()
966 {
967 return(scan_state);
968 }
969 public final void setScanState(int state)
970 {
971 scan_state = state;
972 }
973 public final RowPosition getScanPosition()
974 {
975 return(scan_position);
976 }
977 public final void setScanPosition(RowPosition pos)
978 {
979 scan_position = pos;
980 }
981
982 /**************************************************************************
983 * Public Methods implementing ScanController:
984 **************************************************************************
985 */
986 private void closeScan()
987 throws StandardException
988 {
989 super.close();
990
991 // If we are closed due to catching an error in the middle of init,
992 // xact_manager may not be set yet.
993 if (open_conglom.getXactMgr() != null)
994 open_conglom.getXactMgr().closeMe(this);
995
996 // help the garbage collector.
997 this.init_qualifier = null;
998 init_scanColumnList = null;
999 init_startKeyValue = null;
1000 init_stopKeyValue = null;
1001 }
1002
1003 public void close()
1004 throws StandardException
1005 {
1006 // Finish the scan - this may release locks if read committed and scan
1007 // still holds some locks, and close comes before scan.next() returned
1008 // that scan was done.
1009 positionAtDoneScan(scan_position);
1010
1011 closeScan();
1012 }
1013
1014 public boolean closeForEndTransaction(
1015 boolean closeHeldScan)
1016 throws StandardException
1017 {
1018 if ((!open_conglom.getHold()) || closeHeldScan)
1019 {
1020 // close the scan as part of the commit/abort
1021
1022 this.scan_state = SCAN_DONE;
1023
1024 closeScan();
1025
1026 return(true);
1027 }
1028 else
1029 {
1030 super.close();
1031
1032 // allow the scan to continue after the commit.
1033 // locks and latches will be released as part of the commit, so
1034 // no need to release them by hand.
1035
1036 if (this.scan_state == SCAN_INPROGRESS)
1037 this.scan_state = SCAN_HOLD_INPROGRESS;
1038 else if (this.scan_state == SCAN_INIT)
1039 this.scan_state = SCAN_HOLD_INIT;
1040
1041
1042 return(false);
1043 }
1044 }
1045
1046
1047 /**
1048 @see ScanController#delete
1049 **/
1050 public boolean delete()
1051 throws StandardException
1052 {
1053 repositionScanForUpateOper();
1054
1055 boolean ret_val = true;
1056
1057 // RESOLVE (mikem) - RECID - performance could be better if we did not
1058 // have to call isDeletedAtSlot().
1059
1060 // RESOLVE (mikem) - share code below with conglomerateController.
1061
1062 if (scan_position.current_page.isDeletedAtSlot(
1063 scan_position.current_slot))
1064 {
1065 ret_val = false;
1066 }
1067 else
1068 {
1069 // Delete the row
1070 scan_position.current_page.deleteAtSlot(
1071 scan_position.current_slot, true, (LogicalUndo) null);
1072
1073 if (scan_position.current_page.nonDeletedRecordCount() == 0)
1074 {
1075 queueDeletePostCommitWork(scan_position);
1076 }
1077 }
1078
1079 scan_position.unlatch();
1080
1081 return(ret_val);
1082 }
1083
1084
1085 /**
1086 * A call to allow client to indicate that current row does not qualify.
1087 * <p>
1088 * Indicates to the ScanController that the current row does not
1089 * qualify for the scan. If the isolation level of the scan allows,
1090 * this may result in the scan releasing the lock on this row.
1091 * <p>
1092 * Note that some scan implimentations may not support releasing locks on
1093 * non-qualifying rows, or may delay releasing the lock until sometime
1094 * later in the scan (ie. it may be necessary to keep the lock until
1095 * either the scan is repositioned on the next row or page).
1096 * <p>
1097 * This call should only be made while the scan is positioned on a current
1098 * valid row.
1099 * RESOLVE (mikem-05/29/98) - Implement this when we support levels of
1100 * concurrency less than serializable.
1101 *
1102 * @exception StandardException Standard exception policy.
1103 **/
1104 public void didNotQualify()
1105 throws StandardException
1106 {
1107 }
1108
1109 /**
1110 * Insert all rows that qualify for the current scan into the input
1111 * Hash table.
1112 * <p>
1113 * This routine scans executes the entire scan as described in the
1114 * openScan call. For every qualifying unique row value an entry is
1115 * placed into the HashTable. For unique row values the entry in the
1116 * Hashtable has a key value of the object stored in
1117 * row[key_column_number], and the value of the data is row. For row
1118 * values with duplicates, the key value is also row[key_column_number],
1119 * but the value of the data is a Vector of
1120 * rows. The caller will have to call "instanceof" on the data value
1121 * object if duplicates are expected, to determine if the data value
1122 * of the Hashtable entry is a row or is a Vector of rows.
1123 * <p>
1124 * Note, that for this routine to work efficiently the caller must
1125 * ensure that the object in row[key_column_number] implements
1126 * the hashCode and equals method as appropriate for it's datatype.
1127 * <p>
1128 * It is expected that this call will be the first and only call made in
1129 * an openscan. Qualifiers and stop position of the openscan are applied
1130 * just as in a normal scan. This call is logically equivalent to the
1131 * caller performing the following:
1132 *
1133 * import java.util.Hashtable;
1134 *
1135 * hash_table = new Hashtable();
1136 *
1137 * while (next())
1138 * {
1139 * row = create_new_row();
1140 * fetch(row);
1141 * if ((duplicate_value =
1142 * hash_table.put(row[key_column_number], row)) != null)
1143 * {
1144 * Vector row_vec;
1145 *
1146 * // inserted a duplicate
1147 * if ((duplicate_value instanceof vector))
1148 * {
1149 * row_vec = (Vector) duplicate_value;
1150 * }
1151 * else
1152 * {
1153 * // allocate vector to hold duplicates
1154 * row_vec = new Vector(2);
1155 *
1156 * // insert original row into vector
1157 * row_vec.addElement(duplicate_value);
1158 *
1159 * // put the vector as the data rather than the row
1160 * hash_table.put(row[key_column_number], row_vec);
1161 * }
1162 *
1163 * // insert new row into vector
1164 * row_vec.addElement(row);
1165 * }
1166 * }
1167 * <p>
1168 * The columns of the row will be the standard columns returned as
1169 * part of a scan, as described by the validColumns - see openScan for
1170 * description.
1171 * RESOLVE - is this ok? or should I hard code somehow the row to
1172 * be the first column and the row location?
1173 * <p>
1174 * Currently it is only possible to hash on the first column in the
1175 * conglomerate, in the future we may change the interface to allow
1176 * hashing either on a different column or maybe on a combination of
1177 * columns.
1178 * <p>
1179 * No overflow to external storage is provided, so calling this routine
1180 * on a 1 gigabyte conglomerate will incur at least 1 gigabyte of memory
1181 * (probably failing with a java out of memory condition). If this
1182 * routine gets an out of memory condition, or if "max_rowcnt" is
1183 * exceeded then then the routine will give up, empty the Hashtable,
1184 * and return "false."
1185 * <p>
1186 * On exit from this routine, whether the fetchSet() succeeded or not
1187 * the scan is complete, it is positioned just the same as if the scan
1188 * had been drained by calling "next()" until it returns false (ie.
1189 * fetchNext() and next() calls will return false).
1190 * reopenScan() can be called to restart the scan.
1191 * <p>
1192 *
1193 * RESOLVE - until we get row counts what should we do for sizing the
1194 * the size, capasity, and load factor of the hash table.
1195 * For now it is up to the caller to create the Hashtable,
1196 * Access does not reset any parameters.
1197 * <p>
1198 * RESOLVE - I am not sure if access should be in charge of allocating
1199 * the new row objects. I know that I can do this in the
1200 * case of btree's, but I don't think I can do this in heaps.
1201 * Maybe this is solved by work to be done on the sort
1202 * interface.
1203 *
1204 *
1205 * @return boolean indicating that the fetch set succeeded. If it failed
1206 * Hashtable.clear() will be called leaving an empty
1207 * table.
1208 *
1209 * @param max_rowcnt The maximum number of rows to insert into the
1210 * Hash table. Pass in -1 if there is no maximum.
1211 * @param key_column_numbers The column numbers of the columns in the
1212 * scan result row to be the key to the Hashtable.
1213 * "0" is the first column in the scan result
1214 * row (which may be different than the first
1215 * column in the row in the table of the scan).
1216 * @param hash_table The java HashTable to load into.
1217 *
1218 * @exception StandardException Standard exception policy.
1219 **/
1220 public void fetchSet(
1221 long max_rowcnt,
1222 int[] key_column_numbers,
1223 BackingStoreHashtable hash_table)
1224 throws StandardException
1225 {
1226 fetchRows(
1227 (DataValueDescriptor[][]) null,
1228 (RowLocation[]) null,
1229 hash_table,
1230 max_rowcnt,
1231 key_column_numbers);
1232
1233 return;
1234 }
1235
1236 /**
1237 Reposition the current scan. This call is semantically the same as if
1238 the current scan had been closed and a openScan() had been called instead.
1239 The scan is reopened with against the same conglomerate, and the scan
1240 is reopened with the same "hold" and "forUpdate" parameters passed in
1241 the original openScan. The previous template row continues to be used.
1242
1243 @param startKeyValue An indexable row which holds a
1244 (partial) key value which, in combination with the
1245 startSearchOperator, defines the starting position of
1246 the scan. If null, the starting position of the scan
1247 is the first row of the conglomerate.
1248
1249 @param startSearchOperator an operator which defines
1250 how the startKeyValue is to be searched for. If
1251 startSearchOperator is ScanController.GE, the scan starts on
1252 the first row which is greater than or equal to the
1253 startKeyValue. If startSearchOperation is ScanController.GT,
1254 the scan starts on the first row whose key is greater than
1255 startKeyValue. The startSearchOperation parameter is
1256 ignored if the startKeyValue parameter is null.
1257
1258 @param qualifier An array of qualifiers which, applied
1259 to each key, restrict the rows returned by the scan. Rows
1260 for which any one of the qualifiers returns false are not
1261 returned by the scan. If null, all rows are returned.
1262
1263 @param stopKeyValue An indexable row which holds a
1264 (partial) key value which, in combination with the
1265 stopSearchOperator, defines the ending position of
1266 the scan. If null, the ending position of the scan
1267 is the last row of the conglomerate.
1268
1269 @param stopSearchOperator an operator which defines
1270 how the stopKeyValue is used to determine the scan stopping
1271 position. If stopSearchOperation is ScanController.GE, the scan
1272 stops just before the first row which is greater than or
1273 equal to the stopKeyValue. If stopSearchOperation is
1274 ScanController.GT, the scan stops just before the first row whose
1275 key is greater than startKeyValue. The stopSearchOperation
1276 parameter is ignored if the stopKeyValue parameter is null.
1277
1278 @exception StandardException Standard exception policy.
1279 **/
1280 public void reopenScan(
1281 DataValueDescriptor[] startKeyValue,
1282 int startSearchOperator,
1283 Qualifier qualifier[][],
1284 DataValueDescriptor[] stopKeyValue,
1285 int stopSearchOperator)
1286 throws StandardException
1287 {
1288 if (SanityManager.DEBUG)
1289 {
1290 if (!open_conglom.getHold())
1291 {
1292 SanityManager.ASSERT(
1293 !open_conglom.isClosed(),
1294 "GenericScanController.reopenScan() called on a non-held closed scan.");
1295 }
1296 }
1297
1298 // initialize scan position parameters at beginning of scan
1299 this.scan_state =
1300 (!open_conglom.getHold() ? SCAN_INIT : SCAN_HOLD_INIT);
1301
1302 scan_position.current_rh = null;
1303 }
1304
1305 /**
1306 @see ScanController#replace
1307 **/
1308 public boolean replace(
1309 DataValueDescriptor[] row,
1310 FormatableBitSet validColumns)
1311 throws StandardException
1312 {
1313 repositionScanForUpateOper();
1314
1315 boolean ret_val =
1316 scan_position.current_page.update(
1317 scan_position.current_rh, row, validColumns);
1318
1319 scan_position.unlatch();
1320
1321 return(ret_val);
1322 }
1323
1324 /**
1325 Returns true if the current position of the scan still qualifies
1326 under the set of qualifiers passed to the openScan(). When called
1327 this routine will reapply all qualifiers against the row currently
1328 positioned and return true if the row still qualifies. If the row
1329 has been deleted or no longer passes the qualifiers then this routine
1330 will return false.
1331
1332 This case can come about if the current scan
1333 or another scan on the same table in the same transaction
1334 deleted the row or changed columns referenced by the qualifier after
1335 the next() call which positioned the scan at this row.
1336
1337 Note that for comglomerates which don't support update, like btree's,
1338 there is no need to recheck the qualifiers.
1339
1340 The results of a fetch() performed on a scan positioned on
1341 a deleted row are undefined.
1342
1343 @exception StandardException Standard exception policy.
1344 **/
1345 public boolean doesCurrentPositionQualify()
1346 throws StandardException
1347 {
1348 if (scan_state != SCAN_INPROGRESS)
1349 throw StandardException.newException(
1350 SQLState.AM_SCAN_NOT_POSITIONED);
1351
1352 if (!open_conglom.latchPage(scan_position))
1353 {
1354 return(false);
1355 }
1356
1357 DataValueDescriptor row[] =
1358 open_conglom.getRuntimeMem().get_scratch_row();
1359
1360 // If fetchFromSlot returns null it either means the row is deleted,
1361 // or the qualifier evaluates to false.
1362
1363 boolean ret_val =
1364 (scan_position.current_page.fetchFromSlot(
1365 scan_position.current_rh,
1366 scan_position.current_slot,
1367 row,
1368 init_fetchDesc,
1369 false) != null);
1370
1371 scan_position.unlatch();
1372
1373 return(ret_val);
1374 }
1375
1376 /**
1377 @see ScanController#fetch
1378 **/
1379 public void fetch(DataValueDescriptor[] row)
1380 throws StandardException
1381 {
1382 if (scan_state != SCAN_INPROGRESS)
1383 throw StandardException.newException(
1384 SQLState.AM_SCAN_NOT_POSITIONED);
1385
1386 if (!open_conglom.latchPage(scan_position))
1387 {
1388 throw StandardException.newException(
1389 SQLState.AM_RECORD_NOT_FOUND,
1390 open_conglom.getContainer().getId(),
1391 new Long(scan_position.current_rh.getId()));
1392 }
1393
1394 // RESOLVE (mikem) - should this call apply the qualifiers again?
1395 RecordHandle rh =
1396 scan_position.current_page.fetchFromSlot(
1397 scan_position.current_rh,
1398 scan_position.current_slot,
1399 row,
1400 init_fetchDesc,
1401 false);
1402
1403 scan_position.unlatch();
1404
1405 if (rh == null)
1406 {
1407 /*
1408 if (SanityManager.DEBUG)
1409 {
1410 if (isCurrentPositionDeleted())
1411 SanityManager.THROWASSERT(
1412 "The record (" +
1413 open_conglom.getContainer().getId() +
1414 ", " +
1415 scan_position.current_rh.getPageNumber() + ", " +
1416 scan_position.current_rh.getId() + ") " +
1417 "being fetched is marked deleted on page.:\n");
1418 }
1419 */
1420
1421 throw StandardException.newException(
1422 SQLState.AM_RECORD_NOT_FOUND,
1423 open_conglom.getContainer().getId(),
1424 new Long(scan_position.current_rh.getId()));
1425 }
1426
1427 return;
1428 }
1429
1430 /**
1431 Fetch the location of the current position in the scan.
1432 @see ScanController#fetchLocation
1433
1434 @exception StandardException Standard exception policy.
1435 **/
1436 public void fetchLocation(RowLocation templateLocation)
1437 throws StandardException
1438 {
1439 throw StandardException.newException(
1440 SQLState.BTREE_UNIMPLEMENTED_FEATURE);
1441 }
1442
1443 /**
1444 * Return ScanInfo object which describes performance of scan.
1445 * <p>
1446 * Return ScanInfo object which contains information about the current
1447 * scan.
1448 * <p>
1449 *
1450 * @see ScanInfo
1451 *
1452 * @return The ScanInfo object which contains info about current scan.
1453 *
1454 * @exception StandardException Standard exception policy.
1455 **/
1456 public ScanInfo getScanInfo()
1457 throws StandardException
1458 {
1459 throw StandardException.newException(
1460 SQLState.BTREE_UNIMPLEMENTED_FEATURE);
1461 }
1462
1463
1464
1465 /**
1466 Returns true if the current position of the scan is at a
1467 deleted row. This case can come about if the current scan
1468 or another scan on the same table in the same transaction
1469 deleted the row after the next() call which positioned the
1470 scan at this row.
1471
1472 The results of a fetch() performed on a scan positioned on
1473 a deleted row are undefined.
1474
1475 @exception StandardException Standard exception policy.
1476 **/
1477 public boolean isCurrentPositionDeleted()
1478 throws StandardException
1479 {
1480 if (scan_state != SCAN_INPROGRESS)
1481 throw StandardException.newException(
1482 SQLState.AM_SCAN_NOT_POSITIONED);
1483
1484 if (!open_conglom.latchPage(scan_position))
1485 {
1486 return(true);
1487 }
1488
1489 boolean ret_val =
1490 scan_position.current_page.isDeletedAtSlot(
1491 scan_position.current_slot);
1492
1493 scan_position.unlatch();
1494
1495 return(ret_val);
1496 }
1497}