KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > sql > execute > ScrollInsensitiveResultSet


1 /*
2
3    Derby - Class org.apache.derby.impl.sql.execute.ScrollInsensitiveResultSet
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.sql.execute;
23
24 import org.apache.derby.iapi.services.loader.GeneratedMethod;
25
26 import org.apache.derby.iapi.services.monitor.Monitor;
27
28 import org.apache.derby.iapi.services.sanity.SanityManager;
29
30 import org.apache.derby.iapi.sql.execute.CursorResultSet;
31 import org.apache.derby.iapi.sql.execute.ExecRow;
32 import org.apache.derby.iapi.sql.execute.ExecutionContext;
33 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
34
35 import org.apache.derby.iapi.sql.Activation;
36 import org.apache.derby.iapi.sql.Row;
37
38 import org.apache.derby.iapi.store.access.ConglomerateController;
39 import org.apache.derby.iapi.store.access.ScanController;
40 import org.apache.derby.iapi.store.access.TransactionController;
41
42 import org.apache.derby.iapi.types.RowLocation;
43 import org.apache.derby.iapi.types.DataValueDescriptor;
44
45 import org.apache.derby.iapi.error.StandardException;
46 import org.apache.derby.iapi.reference.SQLState;
47
48 import org.apache.derby.iapi.store.access.BackingStoreHashtable;
49 import org.apache.derby.iapi.services.io.FormatableBitSet;
50
51 import org.apache.derby.iapi.types.SQLBoolean;
52 import org.apache.derby.iapi.types.SQLInteger;
53
54 /**
55  *
56  * Provide insensitive scrolling functionality for the underlying
57  * result set. We build a disk backed hash table of rows as the
58  * user scrolls forward, with the position as the key.
59  *
60  * For read-only result sets the hash table will containg the
61  * following columns:
62  *<pre>
63  * +-------------------------------+
64  * | KEY |
65  * +-------------------------------+
66  * | Row |
67  * +-------------------------------+
68  *</pre>
69  * where key is the position of the row in the result set and row is the data.
70  *
71  * And for updatable result sets it will contain:
72  * <pre>
73  * +-------------------------------+
74  * | KEY | [0]
75  * +-------------------------------+
76  * | RowLocation | [POS_ROWLOCATION]
77  * +-------------------------------+
78  * | Deleted | [POS_ROWDELETED]
79  * +-------------------------------+
80  * | Updated | [POS_ROWUPDATED]
81  * +-------------------------------+
82  * | Row | [extraColumns ... n]
83  * +-------------------------------+
84  *</pre>
85  * where key is the position of the row in the result set, rowLocation is
86  * the row location of that row in the Heap, Deleted indicates whether the
87  * row has been deleted, Updated indicates whether the row has been updated,
88  * and row is the data.
89  *
90  */

91
92 public class ScrollInsensitiveResultSet extends NoPutResultSetImpl
93     implements CursorResultSet
94 {
95     /*
96     ** Set in constructor and not altered during life of object.
97     */

98
99     public NoPutResultSet source;
100
101
102
103     private int sourceRowWidth;
104
105     private BackingStoreHashtable ht;
106     private ExecRow resultRow;
107
108     // Scroll tracking
109
private int positionInSource;
110     private int currentPosition;
111     private int lastPosition;
112     private boolean seenFirst;
113     private boolean seenLast;
114     private boolean beforeFirst = true;
115     private boolean afterLast;
116
117     public int numFromHashTable;
118     public int numToHashTable;
119
120     private int maxRows;
121
122     private boolean keepAfterCommit;
123
124     /* The hash table will contain a different number of extra columns depending
125      * on whether the result set is updatable or not.
126      * extraColumns will contain the number of extra columns on the hash table,
127      * 1 for read-only result sets and LAST_EXTRA_COLUMN + 1 for updatable
128      * result sets.
129      */

130     private int extraColumns;
131     
132     /* positionInHashTable is used for getting a row from the hash table. Prior
133      * to getting the row, positionInHashTable will be set to the desired KEY.
134      */

135     private SQLInteger positionInHashTable;
136
137     /* Reference to the target result set. Target is used for updatable result
138      * sets in order to keep the target result set on the same row as the
139      * ScrollInsensitiveResultSet.
140      */

141     private CursorResultSet target;
142
143     /* If the last row was fetched from the HashTable, updatable result sets
144      * need to be positioned in the last fetched row before resuming the
145      * fetch from core.
146      */

147     private boolean needsRepositioning;
148
149     /* Position of the different fields in the hash table row for updatable
150      * result sets
151      */

152     private static final int POS_ROWLOCATION = 1;
153     private static final int POS_ROWDELETED = 2;
154     private static final int POS_ROWUPDATED = 3;
155     private static final int LAST_EXTRA_COLUMN = 3;
156
157     /**
158      * Constructor for a ScrollInsensitiveResultSet
159      *
160      * @param source The NoPutResultSet from which to get rows
161      * to scroll through
162      * @param activation The activation for this execution
163      * @param resultSetNumber The resultSetNumber
164      * @param sourceRowWidth # of columns in the source row
165      *
166      * @exception StandardException on error
167      */

168
169     public ScrollInsensitiveResultSet(NoPutResultSet source,
170                               Activation activation, int resultSetNumber,
171                               int sourceRowWidth,
172                               double optimizerEstimatedRowCount,
173                               double optimizerEstimatedCost) throws StandardException
174     {
175         super(activation, resultSetNumber,
176               optimizerEstimatedRowCount, optimizerEstimatedCost);
177         this.source = source;
178         this.sourceRowWidth = sourceRowWidth;
179         keepAfterCommit = activation.getResultSetHoldability();
180         maxRows = activation.getMaxRows();
181         if (SanityManager.DEBUG)
182         {
183             SanityManager.ASSERT(maxRows != -1,
184                 "maxRows not expected to be -1");
185         }
186
187         constructorTime += getElapsedMillis(beginTime);
188
189         positionInHashTable = new SQLInteger();
190         needsRepositioning = false;
191         if (isForUpdate()) {
192             target = ((CursorActivation)activation).getTargetResultSet();
193             extraColumns = LAST_EXTRA_COLUMN + 1;
194         } else {
195             target = null;
196             extraColumns = 1;
197         }
198     }
199
200
201     //
202
// ResultSet interface (leftover from NoPutResultSet)
203
//
204

205     /**
206      * open a scan on the source. scan parameters are evaluated
207      * at each open, so there is probably some way of altering
208      * their values...
209      *
210      * @exception StandardException thrown on failure
211      */

212     public void openCore() throws StandardException
213     {
214         beginTime = getCurrentTimeMillis();
215         if (SanityManager.DEBUG)
216             SanityManager.ASSERT( ! isOpen, "ScrollInsensitiveResultSet already open");
217
218         source.openCore();
219         isOpen = true;
220         numOpens++;
221
222         /* Create the hash table. We pass
223          * null in as the row source as we will
224          * build the hash table on demand as
225          * the user scrolls.
226          * The 1st column, the position in the
227          * scan, will be the key column.
228          */

229         final int[] keyCols = new int[] { 0 };
230         
231         /* We don't use the optimizer row count for this because it could be
232          * wildly pessimistic. We only use Hash tables when the optimizer row count
233          * is within certain bounds. We have no alternative for scrolling insensitive
234          * cursors so we'll just trust that it will fit.
235          * We need BackingStoreHashtable to actually go to disk when it doesn't fit.
236          * This is a known limitation.
237          */

238         ht = new BackingStoreHashtable(getTransactionController(),
239                                        null,
240                                        keyCols,
241                                        false,
242                                         -1, // don't trust optimizer row count
243
HashScanResultSet.DEFAULT_MAX_CAPACITY,
244                                        HashScanResultSet.DEFAULT_INITIAL_CAPACITY,
245                                        HashScanResultSet.DEFAULT_MAX_CAPACITY,
246                                        false,
247                                        keepAfterCommit);
248
249         openTime += getElapsedMillis(beginTime);
250         setBeforeFirstRow();
251     }
252
253     /**
254      * reopen a scan on the table. scan parameters are evaluated
255      * at each open, so there is probably some way of altering
256      * their values...
257      *
258      * @exception StandardException thrown if cursor finished.
259      */

260     public void reopenCore() throws StandardException
261     {
262         boolean constantEval = true;
263
264         beginTime = getCurrentTimeMillis();
265
266         if (SanityManager.DEBUG)
267         {
268             SanityManager.ASSERT(isOpen, "ScrollInsensitiveResultSet already open");
269             SanityManager.THROWASSERT(
270                 "reopenCore() not expected to be called");
271         }
272         setBeforeFirstRow();
273     }
274
275     /**
276      * Returns the row at the absolute position from the query,
277      * and returns NULL when there is no such position.
278      * (Negative position means from the end of the result set.)
279      * Moving the cursor to an invalid position leaves the cursor
280      * positioned either before the first row (negative position)
281      * or after the last row (positive position).
282      * NOTE: An exception will be thrown on 0.
283      *
284      * @param row The position.
285      * @return The row at the absolute position, or NULL if no such position.
286      *
287      * @exception StandardException Thrown on failure
288      * @see Row
289      */

290     public ExecRow getAbsoluteRow(int row) throws StandardException
291     {
292         if ( ! isOpen )
293         {
294             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "absolute");
295         }
296
297         attachStatementContext();
298
299         if (SanityManager.DEBUG)
300         {
301             if (!isTopResultSet)
302             {
303                 SanityManager.THROWASSERT(
304                     this + "expected to be the top ResultSet");
305             }
306         }
307
308                 // Absolute 0 is defined to be before first!
309
if (row == 0)
310         {
311                     setBeforeFirstRow();
312                     return null;
313         }
314
315         if (seenLast && row > lastPosition) {
316            return setAfterLastRow();
317         }
318
319         if (row > 0)
320         {
321             // position is from the start of the result set
322
if (row <= positionInSource)
323             {
324                 // We've already seen the row before
325
return getRowFromHashTable(row);
326             }
327             
328             /* We haven't seen the row yet, scan until we find
329              * it or we get to the end.
330              */

331             int diff = row - positionInSource;
332             ExecRow result = null;
333             while (diff > 0)
334             {
335                 if ((result = getNextRowFromSource()) != null)
336                 {
337                     diff--;
338                 }
339                 else
340                 {
341                     break;
342                 }
343             }
344             if (result != null) {
345                 result = getRowFromHashTable(row);
346             }
347             currentRow = result;
348             return result;
349         }
350         else if (row < 0)
351         {
352             // position is from the end of the result set
353

354             // Get the last row, if we haven't already
355
if (!seenLast)
356             {
357                 getLastRow();
358             }
359
360             // Note, for negative values position is from beyond the end
361
// of the result set, e.g. absolute(-1) points to the last row
362
int beyondResult = lastPosition + 1;
363             if (beyondResult + row > 0)
364             {
365                 // valid row
366
return getRowFromHashTable(beyondResult + row);
367             }
368             else
369             {
370                 // position before the beginning of the result set
371
return setBeforeFirstRow();
372             }
373         }
374  
375         currentRow = null;
376         return null;
377     }
378
379     /**
380      * Returns the row at the relative position from the current
381      * cursor position, and returns NULL when there is no such position.
382      * (Negative position means toward the beginning of the result set.)
383      * Moving the cursor to an invalid position leaves the cursor
384      * positioned either before the first row (negative position)
385      * or after the last row (positive position).
386      * NOTE: 0 is valid.
387      * NOTE: An exception is thrown if the cursor is not currently
388      * positioned on a row.
389      *
390      * @param row The position.
391      * @return The row at the relative position, or NULL if no such position.
392      *
393      * @exception StandardException Thrown on failure
394      * @see Row
395      */

396     public ExecRow getRelativeRow(int row) throws StandardException
397     {
398         if ( ! isOpen )
399         {
400             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "relative");
401         }
402
403         attachStatementContext();
404
405         if (SanityManager.DEBUG)
406         {
407             if (!isTopResultSet)
408             {
409                 SanityManager.THROWASSERT(
410                     this + "expected to be the top ResultSet");
411             }
412         }
413
414         // Return the current row for 0
415
if (row == 0)
416         {
417                     if (beforeFirst || afterLast || currentPosition==0) {
418                         return null;
419                     } else {
420             return getRowFromHashTable(currentPosition);
421                     }
422         }
423         else if (row > 0)
424         {
425             return getAbsoluteRow(currentPosition + row);
426         }
427         else
428         {
429             // row < 0
430
if (currentPosition + row < 0)
431             {
432                 return setBeforeFirstRow();
433             }
434             return getAbsoluteRow(currentPosition + row);
435         }
436     }
437
438     /**
439      * Sets the current position to before the first row and returns NULL
440      * because there is no current row.
441      *
442      * @return NULL.
443      *
444      * @see Row
445      */

446     public ExecRow setBeforeFirstRow()
447     {
448         currentPosition = 0;
449         beforeFirst = true;
450         afterLast = false;
451         currentRow = null;
452         return null;
453     }
454
455     /**
456      * Returns the first row from the query, and returns NULL when there
457      * are no rows.
458      *
459      * @return The first row, or NULL if no rows.
460      *
461      * @exception StandardException Thrown on failure
462      * @see Row
463      */

464     public ExecRow getFirstRow()
465         throws StandardException
466     {
467         if ( ! isOpen )
468         {
469             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "first");
470         }
471
472         /* Get the row from the hash table if
473          * we have already seen it before.
474          */

475         if (seenFirst)
476         {
477             return getRowFromHashTable(1);
478         }
479
480         attachStatementContext();
481
482         if (SanityManager.DEBUG)
483         {
484             if (!isTopResultSet)
485             {
486                 SanityManager.THROWASSERT(
487                     this + "expected to be the top ResultSet");
488             }
489         }
490
491         return getNextRowCore();
492     }
493
494     /**
495      *
496      * @exception StandardException thrown on failure
497      */

498     public ExecRow getNextRowCore() throws StandardException
499     {
500         ExecRow result = null;
501
502         beginTime = getCurrentTimeMillis();
503         if (!isOpen)
504             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
505
506         if (seenLast && currentPosition == lastPosition) {
507            return setAfterLastRow();
508         }
509
510         /* Should we get the next row from the source or the hash table? */
511         if (currentPosition == positionInSource)
512         {
513             /* Current position is same as position in source.
514              * Get row from the source.
515              */

516             result = getNextRowFromSource();
517             if (result !=null) {
518                 result = getRowFromHashTable(currentPosition);
519             }
520         }
521         else if (currentPosition < positionInSource)
522         {
523             /* Current position is before position in source.
524              * Get row from the hash table.
525              */

526             result = getRowFromHashTable(currentPosition + 1);
527         }
528         else
529         {
530             result = null;
531         }
532
533         if (result != null)
534         {
535             rowsSeen++;
536             afterLast = false;
537         }
538
539         currentRow = result;
540         setCurrentRow(currentRow);
541         beforeFirst = false;
542
543         nextTime += getElapsedMillis(beginTime);
544
545         return result;
546     }
547
548     /**
549      * Returns the previous row from the query, and returns NULL when there
550      * are no more previous rows.
551      *
552      * @return The previous row, or NULL if no more previous rows.
553      *
554      * @exception StandardException Thrown on failure
555      * @see Row
556      */

557     public ExecRow getPreviousRow()
558         throws StandardException
559     {
560         if ( ! isOpen )
561         {
562             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
563         }
564
565         if (SanityManager.DEBUG)
566         {
567             if (!isTopResultSet)
568             {
569                 SanityManager.THROWASSERT(
570                     this + "expected to be the top ResultSet");
571             }
572         }
573
574         /* No row if we are positioned before the first row
575          * or the result set is empty.
576          */

577         if (beforeFirst || currentPosition == 0)
578         {
579             currentRow = null;
580             return null;
581         }
582
583         // Get the last row, if we are after it
584
if (afterLast)
585         {
586             // Special case for empty tables
587
if (lastPosition == 0)
588             {
589                 afterLast = false;
590                 beforeFirst = false;
591                 currentRow = null;
592                 return null;
593             }
594             else
595             {
596                 return getRowFromHashTable(lastPosition);
597             }
598         }
599
600         // Move back 1
601
currentPosition--;
602         if (currentPosition == 0)
603         {
604             setBeforeFirstRow();
605             return null;
606         }
607         return getRowFromHashTable(currentPosition);
608     }
609
610     /**
611      * Returns the last row from the query, and returns NULL when there
612      * are no rows.
613      *
614      * @return The last row, or NULL if no rows.
615      *
616      * @exception StandardException Thrown on failure
617      * @see Row
618      */

619     public ExecRow getLastRow()
620         throws StandardException
621     {
622         if ( ! isOpen )
623         {
624             throw StandardException.newException(SQLState.LANG_RESULT_SET_NOT_OPEN, "next");
625         }
626         
627         if (!seenLast)
628         {
629             attachStatementContext();
630
631             if (SanityManager.DEBUG)
632             {
633                 if (!isTopResultSet)
634                 {
635                     SanityManager.THROWASSERT(
636                                               this + "expected to be the top ResultSet");
637                 }
638             }
639             
640             /* Scroll to the end, filling the hash table as
641              * we scroll, and return the last row that we find.
642              */

643             ExecRow result = null;
644             while ((result = getNextRowFromSource()) != null);
645         }
646         
647         if (SanityManager.DEBUG && !seenLast)
648         {
649             SanityManager.THROWASSERT(this + "expected to have seen last");
650         }
651         
652         beforeFirst = false;
653         afterLast = false;
654
655         // Special case if table is empty
656
if (lastPosition == 0)
657         {
658             currentRow = null;
659             return null;
660         }
661         else
662         {
663             return getRowFromHashTable(lastPosition);
664         }
665     }
666
667     /**
668      * Sets the current position to after the last row and returns NULL
669      * because there is no current row.
670      *
671      * @return NULL.
672      *
673      * @exception StandardException Thrown on failure
674      * @see Row
675      */

676     public ExecRow setAfterLastRow()
677         throws StandardException
678     {
679         if (! seenLast)
680         {
681             getLastRow();
682         }
683         if (lastPosition == 0) {
684            // empty rs special case
685
currentPosition = 0;
686            afterLast = false;
687         } else {
688            currentPosition = lastPosition + 1;
689            afterLast = true;
690         }
691
692         beforeFirst = false;
693         currentRow = null;
694         return null;
695     }
696
697     /**
698      * Determine if the cursor is before the first row in the result
699      * set.
700      *
701      * @return true if before the first row, false otherwise. Returns
702      * false when the result set contains no rows.
703      * @exception StandardException Thrown on error.
704      */

705    public boolean checkRowPosition(int isType) throws StandardException
706     {
707         switch (isType) {
708         case ISBEFOREFIRST:
709
710             if (! beforeFirst)
711             {
712                 return false;
713             }
714
715             // Spec says to return false if result set is empty
716
if (seenFirst)
717             {
718                 return true;
719             }
720             else
721             {
722                 ExecRow firstRow = getFirstRow();
723                 if (firstRow == null)
724                 {
725                     // ResultSet is empty
726
return false;
727                 }
728                 else
729                 {
730                     // ResultSet is not empty - reset position
731
getPreviousRow();
732                     return true;
733                 }
734             }
735         case ISFIRST:
736             return (currentPosition == 1);
737         case ISLAST:
738             if (beforeFirst || afterLast || currentPosition==0 ||
739                 currentPosition<positionInSource)
740             {
741                 return false;
742             }
743             
744             /* If we have seen the last row, we can tell if we are
745              * on it by comparing currentPosition with lastPosition.
746              * Otherwise, we check if there is a next row.
747              */

748             if (seenLast)
749             {
750                 return (currentPosition == lastPosition);
751             }
752             else
753             {
754                 final int savePosition = currentPosition;
755                 final boolean retval = (getNextRowFromSource() == null);
756                 getRowFromHashTable(savePosition);
757                 return retval;
758             }
759         case ISAFTERLAST:
760             return afterLast;
761         default:
762             return false;
763         }
764     }
765
766     /**
767      * Returns the row number of the current row. Row
768      * numbers start from 1 and go to 'n'. Corresponds
769      * to row numbering used to position current row
770      * in the result set (as per JDBC).
771      *
772      * @return the row number, or 0 if not on a row
773      *
774      */

775     public int getRowNumber()
776     {
777         return currentRow == null ? 0 : currentPosition;
778     }
779
780     /* Get the next row from the source ResultSet tree and insert into the hash table */
781     private ExecRow getNextRowFromSource() throws StandardException
782     {
783         ExecRow sourceRow = null;
784         ExecRow result = null;
785
786         /* Don't give back more rows than requested */
787         if (maxRows > 0 && maxRows == positionInSource)
788         {
789             seenLast = true;
790             lastPosition = positionInSource;
791             afterLast = true;
792             return null;
793         }
794
795
796         if (needsRepositioning) {
797             positionInLastFetchedRow();
798             needsRepositioning = false;
799         }
800         sourceRow = source.getNextRowCore();
801
802         if (sourceRow != null)
803         {
804             seenFirst = true;
805             beforeFirst = false;
806
807             long beginTCTime = getCurrentTimeMillis();
808             /* If this is the first row from the source then we create a new row
809              * for use when fetching from the hash table.
810              */

811             if (resultRow == null)
812             {
813                 resultRow = activation.getExecutionFactory().getValueRow(sourceRowWidth);
814             }
815
816             positionInSource++;
817             currentPosition = positionInSource;
818
819             RowLocation rowLoc = null;
820             if (source.isForUpdate()) {
821                 rowLoc = ((CursorResultSet)source).getRowLocation();
822             }
823
824             addRowToHashTable(sourceRow, currentPosition, rowLoc, false);
825
826         }
827         // Remember whether or not we're past the end of the table
828
else
829         {
830             if (! seenLast)
831             {
832                 lastPosition = positionInSource;
833             }
834             seenLast = true;
835             // Special case for empty table (afterLast is never true)
836
if (positionInSource == 0)
837             {
838                 afterLast = false;
839             }
840             else
841             {
842                 afterLast = true;
843                 currentPosition = positionInSource + 1;
844             }
845         }
846
847         return sourceRow;
848     }
849
850     /**
851      * If the result set has been opened,
852      * close the open scan.
853      *
854      * @exception StandardException thrown on error
855      */

856     public void close() throws StandardException
857     {
858         beginTime = getCurrentTimeMillis();
859         if ( isOpen )
860         {
861             currentRow = null;
862             source.close();
863
864             if (ht != null)
865             {
866                 ht.close();
867                 ht = null;
868             }
869
870             super.close();
871         }
872         else
873             if (SanityManager.DEBUG)
874                 SanityManager.DEBUG("CloseRepeatInfo","Close of ScrollInsensitiveResultSet repeated");
875         setBeforeFirstRow();
876
877         closeTime += getElapsedMillis(beginTime);
878     }
879
880     public void finish() throws StandardException
881     {
882         source.finish();
883         finishAndRTS();
884     }
885
886     /**
887      * Return the total amount of time spent in this ResultSet
888      *
889      * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
890      * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
891      *
892      * @return long The total amount of time spent (in milliseconds).
893      */

894     public long getTimeSpent(int type)
895     {
896         long totTime = constructorTime + openTime + nextTime + closeTime;
897
898         if (type == NoPutResultSet.CURRENT_RESULTSET_ONLY)
899         {
900             return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);
901         }
902         else
903         {
904             return totTime;
905         }
906     }
907
908     //
909
// CursorResultSet interface
910
//
911

912     /**
913      * Gets information from its source. We might want
914      * to have this take a CursorResultSet in its constructor some day,
915      * instead of doing a cast here?
916      *
917      * @see CursorResultSet
918      *
919      * @return the row location of the current cursor row.
920      *
921      * @exception StandardException thrown on failure
922      */

923     public RowLocation getRowLocation() throws StandardException
924     {
925         if (SanityManager.DEBUG)
926             SanityManager.ASSERT(source instanceof CursorResultSet, "source not CursorResultSet");
927         return ( (CursorResultSet)source ).getRowLocation();
928     }
929
930     /**
931      * Gets information from last getNextRow call.
932      *
933      * @see CursorResultSet
934      *
935      * @return the last row returned.
936      */

937     /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
938      * once there is such a method. (currentRow is redundant)
939      */

940     public ExecRow getCurrentRow() throws StandardException
941     {
942         if (isForUpdate() && isDeleted()) {
943             return null;
944         } else {
945             return currentRow;
946         }
947     }
948
949     //
950
// class implementation
951
//
952

953     /**
954      * Add a row to the backing hash table, keyed on position.
955      * When a row gets updated when using scrollable insensitive updatable
956      * result sets, the old entry for the row will be deleted from the hash
957      * table and this method will be called to add the new values for the row
958      * to the hash table, with the parameter rowUpdated = true so as to mark
959      * the row as updated. The latter is done in order to implement
960      * detectability of own changes for result sets of this type.
961      *
962      * @param sourceRow The row to add.
963      * @param position The key
964      * @param rowLoc The rowLocation of the row to add.
965      * @param rowUpdated Indicates whether the row has been updated.
966      *
967      */

968     private void addRowToHashTable(ExecRow sourceRow, int position,
969             RowLocation rowLoc, boolean rowUpdated)
970         throws StandardException
971     {
972         DataValueDescriptor[] hashRowArray = new
973                 DataValueDescriptor[sourceRowWidth + extraColumns];
974         // 1st element is the key
975
hashRowArray[0] = new SQLInteger(position);
976         if (isForUpdate()) {
977             hashRowArray[POS_ROWLOCATION] = rowLoc.getClone();
978             hashRowArray[POS_ROWDELETED] = new SQLBoolean(false);
979             hashRowArray[POS_ROWUPDATED] = new SQLBoolean(rowUpdated);
980         }
981
982         /* Copy rest of elements from sourceRow.
983          * NOTE: We need to clone the source row
984          * and we do our own cloning since the 1st column
985          * is not a wrapper.
986          */

987         DataValueDescriptor[] sourceRowArray = sourceRow.getRowArray();
988
989         System.arraycopy(sourceRowArray, 0, hashRowArray, extraColumns,
990                 sourceRowArray.length);
991
992         ht.put(true, hashRowArray);
993
994         numToHashTable++;
995     }
996
997     /**
998      * Get the row at the specified position
999      * from the hash table.
1000     *
1001     * @param position The specified position.
1002     *
1003     * @return The row at that position.
1004     *
1005     * @exception StandardException thrown on failure
1006     */

1007    private ExecRow getRowFromHashTable(int position)
1008        throws StandardException
1009    {
1010
1011        // Get the row from the hash table
1012
positionInHashTable.setValue(position);
1013        DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1014                ht.get(positionInHashTable);
1015
1016
1017        if (SanityManager.DEBUG)
1018        {
1019            SanityManager.ASSERT(hashRowArray != null,
1020                "hashRowArray expected to be non-null");
1021        }
1022        // Copy out the Object[] without the position.
1023
DataValueDescriptor[] resultRowArray = new
1024                DataValueDescriptor[hashRowArray.length - extraColumns];
1025        System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0,
1026                resultRowArray.length);
1027
1028        resultRow.setRowArray(resultRowArray);
1029
1030        // Reset the current position to the user position
1031
currentPosition = position;
1032
1033        numFromHashTable++;
1034
1035        if (resultRow != null)
1036        {
1037            beforeFirst = false;
1038            afterLast = false;
1039        }
1040
1041        if (isForUpdate()) {
1042            RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1043            // Keep source and target with the same currentRow
1044
((NoPutResultSet)target).setCurrentRow(resultRow);
1045            ((NoPutResultSet)target).positionScanAtRowLocation(rowLoc);
1046            needsRepositioning = true;
1047        }
1048        
1049        setCurrentRow(resultRow);
1050
1051        return resultRow;
1052    }
1053    
1054    /**
1055     * Get the row data at the specified position
1056     * from the hash table.
1057     *
1058     * @param position The specified position.
1059     *
1060     * @return The row data at that position.
1061     *
1062     * @exception StandardException thrown on failure
1063     */

1064    private DataValueDescriptor[] getRowArrayFromHashTable(int position)
1065        throws StandardException
1066    {
1067        positionInHashTable.setValue(position);
1068        final DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1069            ht.get(positionInHashTable);
1070        
1071        // Copy out the Object[] without the position.
1072
final DataValueDescriptor[] resultRowArray = new
1073            DataValueDescriptor[hashRowArray.length - extraColumns];
1074        System.arraycopy(hashRowArray, extraColumns, resultRowArray, 0,
1075                         resultRowArray.length);
1076        return resultRowArray;
1077    }
1078
1079    /**
1080     * Positions the cursor in the last fetched row. This is done before
1081     * navigating to a row that has not previously been fetched, so that
1082     * getNextRowCore() will re-start from where it stopped.
1083     */

1084    private void positionInLastFetchedRow() throws StandardException {
1085        if (positionInSource > 0) {
1086            positionInHashTable.setValue(positionInSource);
1087            DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1088                    ht.get(positionInHashTable);
1089            RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1090            ((NoPutResultSet)target).positionScanAtRowLocation(rowLoc);
1091            currentPosition = positionInSource;
1092        }
1093    }
1094
1095    /**
1096     * @see NoPutResultSet#updateRow
1097     *
1098     * Sets the updated column of the hash table to true and updates the row
1099     * in the hash table with the new values for the row.
1100     */

1101    public void updateRow(ExecRow row) throws StandardException {
1102        ExecRow newRow = row;
1103        boolean undoProjection = false;
1104        
1105        if (source instanceof ProjectRestrictResultSet) {
1106            newRow = ((ProjectRestrictResultSet)source).
1107                doBaseRowProjection(row);
1108            undoProjection = true;
1109        }
1110        positionInHashTable.setValue(currentPosition);
1111        DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1112                ht.get(positionInHashTable);
1113        RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1114        ht.remove(new SQLInteger(currentPosition));
1115        addRowToHashTable(newRow, currentPosition, rowLoc, true);
1116        
1117        // Modify row to refer to data in the BackingStoreHashtable.
1118
// This allows reading of data which goes over multiple pages
1119
// when doing the actual update (LOBs). Putting columns of
1120
// type SQLBinary to disk, has destructive effect on the columns,
1121
// and they need to be re-read. That is the reason this is needed.
1122
if (undoProjection) {
1123            
1124            final DataValueDescriptor[] newRowData = newRow.getRowArray();
1125            
1126            // Array of original position in row
1127
final int[] origPos =((ProjectRestrictResultSet)source).
1128                getBaseProjectMapping();
1129            
1130            // We want the row to contain data backed in BackingStoreHashtable
1131
final DataValueDescriptor[] backedData =
1132                getRowArrayFromHashTable(currentPosition);
1133            
1134            for (int i=0; i<origPos.length; i++) {
1135                if (origPos[i]>=0) {
1136                    row.setColumn(origPos[i], backedData[i]);
1137                }
1138            }
1139        } else {
1140            row.setRowArray(getRowArrayFromHashTable(currentPosition));
1141        }
1142    }
1143
1144    /**
1145     * @see NoPutResultSet#markRowAsDeleted
1146     *
1147     * Sets the deleted column of the hash table to true in the current row.
1148     */

1149    public void markRowAsDeleted() throws StandardException {
1150        positionInHashTable.setValue(currentPosition);
1151        DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1152                ht.get(positionInHashTable);
1153        RowLocation rowLoc = (RowLocation) hashRowArray[POS_ROWLOCATION];
1154        ht.remove(new SQLInteger(currentPosition));
1155        ((SQLBoolean)hashRowArray[POS_ROWDELETED]).setValue(true);
1156        // Set all columns to NULL, the row is now a placeholder
1157
for (int i=extraColumns; i<hashRowArray.length; i++) {
1158            hashRowArray[i].setToNull();
1159        }
1160
1161        ht.put(true, hashRowArray);
1162    }
1163
1164    /**
1165     * Returns TRUE if the row was been deleted within the transaction,
1166     * otherwise returns FALSE
1167     *
1168     * @return True if the row has been deleted, otherwise false
1169     *
1170     * @exception StandardException on error
1171     */

1172    public boolean isDeleted() throws StandardException {
1173        if (currentPosition <= positionInSource && currentPosition > 0) {
1174            positionInHashTable.setValue(currentPosition);
1175            DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1176                    ht.get(positionInHashTable);
1177            return hashRowArray[POS_ROWDELETED].getBoolean();
1178        }
1179        return false;
1180    }
1181
1182    /**
1183     * Returns TRUE if the row was been updated within the transaction,
1184     * otherwise returns FALSE
1185     *
1186     * @return True if the row has been deleted, otherwise false
1187     *
1188     * @exception StandardException on error
1189     */

1190    public boolean isUpdated() throws StandardException {
1191        if (currentPosition <= positionInSource && currentPosition > 0) {
1192            positionInHashTable.setValue(currentPosition);
1193            DataValueDescriptor[] hashRowArray = (DataValueDescriptor[])
1194                    ht.get(positionInHashTable);
1195            return hashRowArray[POS_ROWUPDATED].getBoolean();
1196        }
1197        return false;
1198    }
1199
1200    public boolean isForUpdate() {
1201        return source.isForUpdate();
1202    }
1203
1204}
1205
Popular Tags