KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2
3    Derby - Class org.apache.derby.impl.sql.execute.HashTableResultSet
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.services.io.Storable;
31
32 import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
33 import org.apache.derby.iapi.services.stream.InfoStreams;
34
35 import org.apache.derby.iapi.error.StandardException;
36
37 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
38 import org.apache.derby.iapi.sql.conn.StatementContext;
39
40
41 import org.apache.derby.iapi.sql.execute.CursorResultSet;
42 import org.apache.derby.iapi.sql.execute.ExecRow;
43 import org.apache.derby.iapi.sql.execute.NoPutResultSet;
44
45 import org.apache.derby.iapi.types.DataValueDescriptor;
46 import org.apache.derby.iapi.sql.Activation;
47 import org.apache.derby.iapi.sql.ResultSet;
48
49 import org.apache.derby.iapi.store.access.Qualifier;
50 import org.apache.derby.iapi.store.access.RowSource;
51 import org.apache.derby.iapi.store.access.TransactionController;
52
53 import org.apache.derby.iapi.types.RowLocation;
54
55 import org.apache.derby.iapi.store.access.BackingStoreHashtable;
56 import org.apache.derby.iapi.services.io.FormatableArrayHolder;
57 import org.apache.derby.iapi.services.io.FormatableIntHolder;
58 import org.apache.derby.iapi.store.access.KeyHasher;
59
60 import org.apache.derby.catalog.types.ReferencedColumnsDescriptorImpl;
61
62 import java.util.Properties JavaDoc;
63 import java.util.Vector JavaDoc;
64
65 /**
66  * Builds a hash table on the underlying result set tree.
67  *
68  * @author jerry
69  */

70 class HashTableResultSet extends NoPutResultSetImpl
71     implements CursorResultSet
72 {
73     /* Run time statistics variables */
74     public long restrictionTime;
75     public long projectionTime;
76     public int hashtableSize;
77     public Properties JavaDoc scanProperties;
78
79     // set in constructor and not altered during
80
// life of object.
81
public NoPutResultSet source;
82     public GeneratedMethod singleTableRestriction;
83     public Qualifier[][] nextQualifiers;
84     private GeneratedMethod projection;
85     private int[] projectMapping;
86     private boolean runTimeStatsOn;
87     private ExecRow mappedResultRow;
88     public boolean reuseResult;
89     public int[] keyColumns;
90     private boolean removeDuplicates;
91     private long maxInMemoryRowCount;
92     private int initialCapacity;
93     private float loadFactor;
94     private boolean skipNullKeyColumns;
95
96     // Variable for managing next() logic on hash entry
97
private boolean firstNext = true;
98     private int numFetchedOnNext;
99     private int entryVectorSize;
100     private Vector JavaDoc entryVector;
101
102     private boolean hashTableBuilt;
103     private boolean firstIntoHashtable = true;
104
105     private ExecRow nextCandidate;
106     private ExecRow projRow;
107
108     private BackingStoreHashtable ht;
109
110     //
111
// class interface
112
//
113
HashTableResultSet(NoPutResultSet s,
114                     Activation a,
115                     GeneratedMethod str,
116                     Qualifier[][] nextQualifiers,
117                     GeneratedMethod p,
118                     int resultSetNumber,
119                     int mapRefItem,
120                     boolean reuseResult,
121                     int keyColItem,
122                     boolean removeDuplicates,
123                     long maxInMemoryRowCount,
124                     int initialCapacity,
125                     float loadFactor,
126                     boolean skipNullKeyColumns,
127                     double optimizerEstimatedRowCount,
128                     double optimizerEstimatedCost)
129         throws StandardException
130     {
131         super(a, resultSetNumber, optimizerEstimatedRowCount, optimizerEstimatedCost);
132         source = s;
133         // source expected to be non-null, mystery stress test bug
134
// - sometimes get NullPointerException in openCore().
135
if (SanityManager.DEBUG)
136         {
137             SanityManager.ASSERT(source != null,
138                 "HTRS(), source expected to be non-null");
139         }
140         singleTableRestriction = str;
141         this.nextQualifiers = nextQualifiers;
142         projection = p;
143         projectMapping = ((ReferencedColumnsDescriptorImpl) a.getPreparedStatement().getSavedObject(mapRefItem)).getReferencedColumnPositions();
144         FormatableArrayHolder fah = (FormatableArrayHolder) a.getPreparedStatement().getSavedObject(keyColItem);
145         FormatableIntHolder[] fihArray = (FormatableIntHolder[]) fah.getArray(FormatableIntHolder.class);
146         keyColumns = new int[fihArray.length];
147         for (int index = 0; index < fihArray.length; index++)
148         {
149             keyColumns[index] = fihArray[index].getInt();
150         }
151
152         this.reuseResult = reuseResult;
153         this.removeDuplicates = removeDuplicates;
154         this.maxInMemoryRowCount = maxInMemoryRowCount;
155         this.initialCapacity = initialCapacity;
156         this.loadFactor = loadFactor;
157         this.skipNullKeyColumns = skipNullKeyColumns;
158
159         // Allocate a result row if all of the columns are mapped from the source
160
if (projection == null)
161         {
162             mappedResultRow = activation.getExecutionFactory().getValueRow(projectMapping.length);
163         }
164         constructorTime += getElapsedMillis(beginTime);
165
166         /* Remember whether or not RunTimeStatistics is on */
167         runTimeStatsOn = getLanguageConnectionContext().getRunTimeStatisticsMode();
168     }
169
170     //
171
// NoPutResultSet interface
172
//
173

174     /**
175      * open a scan on the table. scan parameters are evaluated
176      * at each open, so there is probably some way of altering
177      * their values...
178      *
179      * @exception StandardException thrown if cursor finished.
180      */

181     public void openCore() throws StandardException
182     {
183         TransactionController tc;
184
185         beginTime = getCurrentTimeMillis();
186
187         // source expected to be non-null, mystery stress test bug
188
// - sometimes get NullPointerException in openCore().
189
if (SanityManager.DEBUG)
190         {
191             SanityManager.ASSERT(source != null,
192                 "HTRS().openCore(), source expected to be non-null");
193         }
194
195         // REVISIT: through the direct DB API, this needs to be an
196
// error, not an ASSERT; users can open twice. Only through JDBC
197
// is access to open controlled and ensured valid.
198
if (SanityManager.DEBUG)
199             SanityManager.ASSERT( ! isOpen, "HashTableResultSet already open");
200
201         // Get the current transaction controller
202
tc = activation.getTransactionController();
203
204         if (! hashTableBuilt)
205         {
206             source.openCore();
207
208             /* Create and populate the hash table. We pass
209              * ourself in as the row source. This allows us
210              * to apply the single table predicates to the
211              * rows coming from our child as we build the
212              * hash table.
213              */

214             ht = new BackingStoreHashtable(tc,
215                                            this,
216                                            keyColumns,
217                                            removeDuplicates,
218                                            (int) optimizerEstimatedRowCount,
219                                            maxInMemoryRowCount,
220                                            (int) initialCapacity,
221                                            loadFactor,
222                                            skipNullKeyColumns,
223                                            false /* Not kept after a commit */);
224
225             if (runTimeStatsOn)
226             {
227                 hashtableSize = ht.size();
228
229                 if (scanProperties == null)
230                 {
231                     scanProperties = new Properties JavaDoc();
232                 }
233
234                 try
235                 {
236                     if (ht != null)
237                     {
238                         ht.getAllRuntimeStats(scanProperties);
239                     }
240                 }
241                 catch(StandardException se)
242                 {
243                     // ignore
244
}
245             }
246
247             isOpen = true;
248             hashTableBuilt = true;
249         }
250
251         resetProbeVariables();
252
253         numOpens++;
254
255         openTime += getElapsedMillis(beginTime);
256     }
257
258     /**
259      * reopen a scan on the table. scan parameters are evaluated
260      * at each open, so there is probably some way of altering
261      * their values...
262      *
263      * @exception StandardException thrown if cursor finished.
264      */

265     public void reopenCore() throws StandardException
266     {
267
268         if (SanityManager.DEBUG)
269         {
270             SanityManager.ASSERT(isOpen,
271                     "HashTableResultSet already open");
272         }
273
274         beginTime = getCurrentTimeMillis();
275
276         resetProbeVariables();
277
278         numOpens++;
279         openTime += getElapsedMillis(beginTime);
280     }
281
282     private void resetProbeVariables() throws StandardException
283     {
284         firstNext = true;
285         numFetchedOnNext = 0;
286         entryVector = null;
287         entryVectorSize = 0;
288
289         if (nextQualifiers != null)
290         {
291             clearOrderableCache(nextQualifiers);
292         }
293     }
294
295     /**
296      * Return the requested values computed
297      * from the next row (if any) for which
298      * the restriction evaluates to true.
299      * <p>
300      * restriction and projection parameters
301      * are evaluated for each row.
302      *
303      * @exception StandardException thrown on failure.
304      * @exception StandardException ResultSetNotOpen thrown if not yet open.
305      *
306      * @return the next row in the result
307      */

308     public ExecRow getNextRowCore() throws StandardException {
309         ExecRow result = null;
310         DataValueDescriptor[] columns = null;
311
312         beginTime = getCurrentTimeMillis();
313         if ( isOpen )
314         {
315             /* We use a do/while loop to ensure that we continue down
316              * the duplicate chain, if one exists, until we find a
317              * row that matches on all probe predicates (or the
318              * duplicate chain is exhausted.)
319              */

320             do
321             {
322                 if (firstNext)
323                 {
324                     firstNext = false;
325
326                     /* Hash key could be either a single column or multiple
327                      * columns. If a single column, then it is the datavalue
328                      * wrapper, otherwise it is a KeyHasher.
329                      */

330                     Object JavaDoc hashEntry;
331                     if (keyColumns.length == 1)
332                     {
333                         hashEntry = ht.get(nextQualifiers[0][0].getOrderable());
334                     }
335                     else
336                     {
337                         KeyHasher mh =
338                             new KeyHasher(keyColumns.length);
339
340                         for (int index = 0; index < keyColumns.length; index++)
341                         {
342                             // RESOLVE (mikem) - will need to change when we
343
// support OR's in qualifiers.
344
mh.setObject(
345                                 index, nextQualifiers[0][index].getOrderable());
346                         }
347                         hashEntry = ht.get(mh);
348                     }
349
350                     if (hashEntry instanceof Vector JavaDoc)
351                     {
352                         entryVector = (Vector JavaDoc) hashEntry;
353                         entryVectorSize = entryVector.size();
354                         columns =
355                             (DataValueDescriptor[]) entryVector.firstElement();
356                     }
357                     else
358                     {
359                         entryVector = null;
360                         entryVectorSize = 0;
361                         columns = (DataValueDescriptor[]) hashEntry;
362                     }
363                 }
364                 else if (numFetchedOnNext < entryVectorSize)
365                 {
366                     /* We walking a Vector and there's
367                      * more rows left in the vector.
368                      */

369                     columns = (DataValueDescriptor[])
370                         entryVector.elementAt(numFetchedOnNext);
371                 }
372
373                 if (columns != null)
374                 {
375                     if (SanityManager.DEBUG)
376                     {
377                         // Columns is really a Storable[]
378
for (int i = 0; i < columns.length; i++)
379                         {
380                             if (! (columns[0] instanceof Storable))
381                             {
382                                 SanityManager.THROWASSERT(
383                                 "columns[" + i + "] expected to be Storable, not " +
384                                 columns[i].getClass().getName());
385                             }
386                         }
387                     }
388
389                     // See if the entry satisfies all of the other qualifiers
390
boolean qualifies = true;
391
392                     /* We've already "evaluated" the 1st keyColumns qualifiers
393                      * when we probed into the hash table, but we need to
394                      * evaluate them again here because of the behavior of
395                      * NULLs. NULLs are treated as equal when building and
396                      * probing the hash table so that we only get a single
397                      * entry. However, NULL does not equal NULL, so the
398                      * compare() method below will eliminate any row that
399                      * has a key column containing a NULL.
400                      */

401
402                     // RESOLVE (mikem) will have to change when qualifiers
403
// support OR's.
404

405                     if (SanityManager.DEBUG)
406                     {
407                         // we don't support 2 d qualifiers yet.
408
SanityManager.ASSERT(nextQualifiers.length == 1);
409                     }
410                     for (int index = 0; index < nextQualifiers[0].length; index++)
411                     {
412                         Qualifier q = nextQualifiers[0][index];
413
414                         qualifies =
415                             columns[q.getColumnId()].compare(
416                                 q.getOperator(),
417                                 q.getOrderable(),
418                                 q.getOrderedNulls(),
419                                 q.getUnknownRV());
420
421                         if (q.negateCompareResult())
422                         {
423                             qualifies = !(qualifies);
424                         }
425
426                         // Stop if any predicate fails
427
if (! qualifies)
428                         {
429                             break;
430                         }
431                     }
432
433                     if (qualifies)
434                     {
435
436                         for (int index = 0; index < columns.length; index++)
437                         {
438                             nextCandidate.setColumn(index + 1, columns[index]);
439                         }
440
441                         result = doProjection(nextCandidate);
442                     }
443                     else
444                     {
445                         result = null;
446                     }
447
448                     numFetchedOnNext++;
449                 }
450                 else
451                 {
452                     result = null;
453                 }
454             }
455             while (result == null && numFetchedOnNext < entryVectorSize);
456         }
457
458         currentRow = result;
459         setCurrentRow(result);
460
461         nextTime += getElapsedMillis(beginTime);
462
463         if (runTimeStatsOn)
464         {
465             if (! isTopResultSet)
466             {
467                 /* This is simply for RunTimeStats */
468                 /* We first need to get the subquery tracking array via the StatementContext */
469                 StatementContext sc = activation.getLanguageConnectionContext().getStatementContext();
470                 subqueryTrackingArray = sc.getSubqueryTrackingArray();
471             }
472             nextTime += getElapsedMillis(beginTime);
473         }
474         return result;
475     }
476
477     /**
478      * Return the total amount of time spent in this ResultSet
479      *
480      * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
481      * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
482      *
483      * @return long The total amount of time spent (in milliseconds).
484      */

485     public long getTimeSpent(int type)
486     {
487         long totTime = constructorTime + openTime + nextTime + closeTime;
488
489         if (type == CURRENT_RESULTSET_ONLY)
490         {
491             return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);
492         }
493         else
494         {
495             return totTime;
496         }
497     }
498
499     // ResultSet interface
500

501     /**
502      * If the result set has been opened,
503      * close the open scan.
504      *
505      * @exception StandardException thrown on error
506      */

507     public void close() throws StandardException
508     {
509         beginTime = getCurrentTimeMillis();
510         if ( isOpen ) {
511
512             // we don't want to keep around a pointer to the
513
// row ... so it can be thrown away.
514
// REVISIT: does this need to be in a finally
515
// block, to ensure that it is executed?
516
clearCurrentRow();
517
518             source.close();
519
520             super.close();
521
522             if (hashTableBuilt)
523             {
524                 // close the hash table, eating any exception
525
ht.close();
526                 ht = null;
527                 hashTableBuilt = false;
528             }
529         }
530         else
531             if (SanityManager.DEBUG)
532                 SanityManager.DEBUG("CloseRepeatInfo","Close of ProjectRestrictResultSet repeated");
533
534         closeTime += getElapsedMillis(beginTime);
535     }
536
537     //
538
// CursorResultSet interface
539
//
540

541     /**
542      * Gets information from its source. We might want
543      * to have this take a CursorResultSet in its constructor some day,
544      * instead of doing a cast here?
545      *
546      * @see CursorResultSet
547      *
548      * @return the row location of the current cursor row.
549      * @exception StandardException thrown on failure.
550      */

551     public RowLocation getRowLocation() throws StandardException {
552         if (SanityManager.DEBUG)
553             SanityManager.ASSERT(source instanceof CursorResultSet, "source not instance of CursorResultSet");
554         return ( (CursorResultSet)source ).getRowLocation();
555     }
556
557     /**
558      * Gets last row returned.
559      *
560      * @see CursorResultSet
561      *
562      * @return the last row returned.
563      * @exception StandardException thrown on failure.
564      */

565     /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
566      * once there is such a method. (currentRow is redundant)
567      */

568     public ExecRow getCurrentRow() throws StandardException {
569         ExecRow candidateRow = null;
570         ExecRow result = null;
571         boolean restrict = false;
572         DataValueDescriptor restrictBoolean;
573
574         if (SanityManager.DEBUG)
575             SanityManager.ASSERT(isOpen, "PRRS is expected to be open");
576
577         /* Nothing to do if we're not currently on a row */
578         if (currentRow == null)
579         {
580             return null;
581         }
582
583         /* Call the child result set to get it's current row.
584          * If no row exists, then return null, else requalify it
585          * before returning.
586          */

587         candidateRow = ((CursorResultSet) source).getCurrentRow();
588         if (candidateRow != null) {
589             setCurrentRow(candidateRow);
590                 /* If restriction is null, then all rows qualify */
591             restrictBoolean = (DataValueDescriptor)
592                     ((singleTableRestriction == null) ? null : singleTableRestriction.invoke(activation));
593
594             // if the result is null, we make it false --
595
// so the row won't be returned.
596
restrict = (restrictBoolean == null) ||
597                         ((! restrictBoolean.isNull()) &&
598                             restrictBoolean.getBoolean());
599         }
600
601         if (candidateRow != null && restrict)
602         {
603             result = doProjection(candidateRow);
604         }
605
606         currentRow = result;
607         /* Clear the current row, if null */
608         if (result == null) {
609             clearCurrentRow();
610         }
611
612         return currentRow;
613     }
614
615     /**
616      * Do the projection against the source row. Use reflection
617      * where necessary, otherwise get the source column into our
618      * result row.
619      *
620      * @param sourceRow The source row.
621      *
622      * @return The result of the projection
623      *
624      * @exception StandardException thrown on failure.
625      */

626     private ExecRow doProjection(ExecRow sourceRow)
627         throws StandardException
628     {
629         // No need to use reflection if reusing the result
630
if (reuseResult && projRow != null)
631         {
632             return projRow;
633         }
634
635         ExecRow result;
636
637         // Use reflection to do as much of projection as required
638
if (projection != null)
639         {
640             result = (ExecRow) projection.invoke(activation);
641         }
642         else
643         {
644             result = mappedResultRow;
645         }
646
647         // Copy any mapped columns from the source
648
for (int index = 0; index < projectMapping.length; index++)
649         {
650             if (projectMapping[index] != -1)
651             {
652                 result.setColumn(index + 1, sourceRow.getColumn(projectMapping[index]));
653             }
654         }
655
656         /* We need to reSet the current row after doing the projection */
657         setCurrentRow(result);
658
659         /* Remember the result if reusing it */
660         if (reuseResult)
661         {
662             projRow = result;
663         }
664         return result;
665     }
666
667     // RowSource interface
668

669     /**
670      * @see RowSource#getNextRowFromRowSource
671      * @exception StandardException on error
672      */

673     public DataValueDescriptor[] getNextRowFromRowSource()
674         throws StandardException
675     {
676         ExecRow execRow = source.getNextRowCore();
677
678         /* Use the single table predicates, if any,
679          * to filter out rows while populating the
680          * hash table.
681          */

682         while (execRow != null)
683         {
684             boolean restrict = false;
685             DataValueDescriptor restrictBoolean;
686
687             rowsSeen++;
688
689             /* If restriction is null, then all rows qualify */
690             restrictBoolean = (DataValueDescriptor)
691                     ((singleTableRestriction == null) ? null : singleTableRestriction.invoke(activation));
692
693             // if the result is null, we make it false --
694
// so the row won't be returned.
695
restrict = (restrictBoolean == null) ||
696                         ((! restrictBoolean.isNull()) &&
697                             restrictBoolean.getBoolean());
698             if (!restrict)
699             {
700                 execRow = source.getNextRowCore();
701                 continue;
702             }
703
704             if (targetResultSet != null)
705             {
706                 /* Let the target preprocess the row. For now, this
707                  * means doing an in place clone on any indexed columns
708                  * to optimize cloning and so that we don't try to drain
709                  * a stream multiple times. This is where we also
710                  * enforce any check constraints.
711                  */

712                 clonedExecRow = targetResultSet.preprocessSourceRow(execRow);
713             }
714
715
716             /* Get a single ExecRow of the same size
717              * on the way in so that we have a row
718              * to use on the way out.
719              */

720             if (firstIntoHashtable)
721             {
722                 nextCandidate = activation.getExecutionFactory().getValueRow(execRow.nColumns());
723                 firstIntoHashtable = false;
724             }
725
726             return execRow.getRowArray();
727         }
728
729         return null;
730     }
731
732     /**
733      * Is this ResultSet or it's source result set for update
734      *
735      * @return Whether or not the result set is for update.
736      */

737     public boolean isForUpdate()
738     {
739         if (source == null)
740         {
741             return false;
742         }
743         return source.isForUpdate();
744     }
745
746 }
747
Popular Tags