001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.HashTableResultSet
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.execute;
023:
024: import org.apache.derby.iapi.services.loader.GeneratedMethod;
025:
026: import org.apache.derby.iapi.services.monitor.Monitor;
027:
028: import org.apache.derby.iapi.services.sanity.SanityManager;
029:
030: import org.apache.derby.iapi.services.io.Storable;
031:
032: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
033: import org.apache.derby.iapi.services.stream.InfoStreams;
034:
035: import org.apache.derby.iapi.error.StandardException;
036:
037: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
038: import org.apache.derby.iapi.sql.conn.StatementContext;
039:
040: import org.apache.derby.iapi.sql.execute.CursorResultSet;
041: import org.apache.derby.iapi.sql.execute.ExecRow;
042: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
043:
044: import org.apache.derby.iapi.types.DataValueDescriptor;
045: import org.apache.derby.iapi.sql.Activation;
046: import org.apache.derby.iapi.sql.ResultSet;
047:
048: import org.apache.derby.iapi.store.access.Qualifier;
049: import org.apache.derby.iapi.store.access.RowSource;
050: import org.apache.derby.iapi.store.access.TransactionController;
051:
052: import org.apache.derby.iapi.types.RowLocation;
053:
054: import org.apache.derby.iapi.store.access.BackingStoreHashtable;
055: import org.apache.derby.iapi.services.io.FormatableArrayHolder;
056: import org.apache.derby.iapi.services.io.FormatableIntHolder;
057: import org.apache.derby.iapi.store.access.KeyHasher;
058:
059: import org.apache.derby.catalog.types.ReferencedColumnsDescriptorImpl;
060:
061: import java.util.Properties;
062: import java.util.Vector;
063:
064: /**
065: * Builds a hash table on the underlying result set tree.
066: *
067: * @author jerry
068: */
069: class HashTableResultSet extends NoPutResultSetImpl implements
070: CursorResultSet {
071: /* Run time statistics variables */
072: public long restrictionTime;
073: public long projectionTime;
074: public int hashtableSize;
075: public Properties scanProperties;
076:
077: // set in constructor and not altered during
078: // life of object.
079: public NoPutResultSet source;
080: public GeneratedMethod singleTableRestriction;
081: public Qualifier[][] nextQualifiers;
082: private GeneratedMethod projection;
083: private int[] projectMapping;
084: private boolean runTimeStatsOn;
085: private ExecRow mappedResultRow;
086: public boolean reuseResult;
087: public int[] keyColumns;
088: private boolean removeDuplicates;
089: private long maxInMemoryRowCount;
090: private int initialCapacity;
091: private float loadFactor;
092: private boolean skipNullKeyColumns;
093:
094: // Variable for managing next() logic on hash entry
095: private boolean firstNext = true;
096: private int numFetchedOnNext;
097: private int entryVectorSize;
098: private Vector entryVector;
099:
100: private boolean hashTableBuilt;
101: private boolean firstIntoHashtable = true;
102:
103: private ExecRow nextCandidate;
104: private ExecRow projRow;
105:
106: private BackingStoreHashtable ht;
107:
108: //
109: // class interface
110: //
111: HashTableResultSet(NoPutResultSet s, Activation a,
112: GeneratedMethod str, Qualifier[][] nextQualifiers,
113: GeneratedMethod p, int resultSetNumber, int mapRefItem,
114: boolean reuseResult, int keyColItem,
115: boolean removeDuplicates, long maxInMemoryRowCount,
116: int initialCapacity, float loadFactor,
117: boolean skipNullKeyColumns,
118: double optimizerEstimatedRowCount,
119: double optimizerEstimatedCost) throws StandardException {
120: super (a, resultSetNumber, optimizerEstimatedRowCount,
121: optimizerEstimatedCost);
122: source = s;
123: // source expected to be non-null, mystery stress test bug
124: // - sometimes get NullPointerException in openCore().
125: if (SanityManager.DEBUG) {
126: SanityManager.ASSERT(source != null,
127: "HTRS(), source expected to be non-null");
128: }
129: singleTableRestriction = str;
130: this .nextQualifiers = nextQualifiers;
131: projection = p;
132: projectMapping = ((ReferencedColumnsDescriptorImpl) a
133: .getPreparedStatement().getSavedObject(mapRefItem))
134: .getReferencedColumnPositions();
135: FormatableArrayHolder fah = (FormatableArrayHolder) a
136: .getPreparedStatement().getSavedObject(keyColItem);
137: FormatableIntHolder[] fihArray = (FormatableIntHolder[]) fah
138: .getArray(FormatableIntHolder.class);
139: keyColumns = new int[fihArray.length];
140: for (int index = 0; index < fihArray.length; index++) {
141: keyColumns[index] = fihArray[index].getInt();
142: }
143:
144: this .reuseResult = reuseResult;
145: this .removeDuplicates = removeDuplicates;
146: this .maxInMemoryRowCount = maxInMemoryRowCount;
147: this .initialCapacity = initialCapacity;
148: this .loadFactor = loadFactor;
149: this .skipNullKeyColumns = skipNullKeyColumns;
150:
151: // Allocate a result row if all of the columns are mapped from the source
152: if (projection == null) {
153: mappedResultRow = activation.getExecutionFactory()
154: .getValueRow(projectMapping.length);
155: }
156: constructorTime += getElapsedMillis(beginTime);
157:
158: /* Remember whether or not RunTimeStatistics is on */
159: runTimeStatsOn = getLanguageConnectionContext()
160: .getRunTimeStatisticsMode();
161: }
162:
163: //
164: // NoPutResultSet interface
165: //
166:
167: /**
168: * open a scan on the table. scan parameters are evaluated
169: * at each open, so there is probably some way of altering
170: * their values...
171: *
172: * @exception StandardException thrown if cursor finished.
173: */
174: public void openCore() throws StandardException {
175: TransactionController tc;
176:
177: beginTime = getCurrentTimeMillis();
178:
179: // source expected to be non-null, mystery stress test bug
180: // - sometimes get NullPointerException in openCore().
181: if (SanityManager.DEBUG) {
182: SanityManager
183: .ASSERT(source != null,
184: "HTRS().openCore(), source expected to be non-null");
185: }
186:
187: // REVISIT: through the direct DB API, this needs to be an
188: // error, not an ASSERT; users can open twice. Only through JDBC
189: // is access to open controlled and ensured valid.
190: if (SanityManager.DEBUG)
191: SanityManager.ASSERT(!isOpen,
192: "HashTableResultSet already open");
193:
194: // Get the current transaction controller
195: tc = activation.getTransactionController();
196:
197: if (!hashTableBuilt) {
198: source.openCore();
199:
200: /* Create and populate the hash table. We pass
201: * ourself in as the row source. This allows us
202: * to apply the single table predicates to the
203: * rows coming from our child as we build the
204: * hash table.
205: */
206: ht = new BackingStoreHashtable(tc, this , keyColumns,
207: removeDuplicates, (int) optimizerEstimatedRowCount,
208: maxInMemoryRowCount, (int) initialCapacity,
209: loadFactor, skipNullKeyColumns, false /* Not kept after a commit */);
210:
211: if (runTimeStatsOn) {
212: hashtableSize = ht.size();
213:
214: if (scanProperties == null) {
215: scanProperties = new Properties();
216: }
217:
218: try {
219: if (ht != null) {
220: ht.getAllRuntimeStats(scanProperties);
221: }
222: } catch (StandardException se) {
223: // ignore
224: }
225: }
226:
227: isOpen = true;
228: hashTableBuilt = true;
229: }
230:
231: resetProbeVariables();
232:
233: numOpens++;
234:
235: openTime += getElapsedMillis(beginTime);
236: }
237:
238: /**
239: * reopen a scan on the table. scan parameters are evaluated
240: * at each open, so there is probably some way of altering
241: * their values...
242: *
243: * @exception StandardException thrown if cursor finished.
244: */
245: public void reopenCore() throws StandardException {
246:
247: if (SanityManager.DEBUG) {
248: SanityManager.ASSERT(isOpen,
249: "HashTableResultSet already open");
250: }
251:
252: beginTime = getCurrentTimeMillis();
253:
254: resetProbeVariables();
255:
256: numOpens++;
257: openTime += getElapsedMillis(beginTime);
258: }
259:
260: private void resetProbeVariables() throws StandardException {
261: firstNext = true;
262: numFetchedOnNext = 0;
263: entryVector = null;
264: entryVectorSize = 0;
265:
266: if (nextQualifiers != null) {
267: clearOrderableCache(nextQualifiers);
268: }
269: }
270:
271: /**
272: * Return the requested values computed
273: * from the next row (if any) for which
274: * the restriction evaluates to true.
275: * <p>
276: * restriction and projection parameters
277: * are evaluated for each row.
278: *
279: * @exception StandardException thrown on failure.
280: * @exception StandardException ResultSetNotOpen thrown if not yet open.
281: *
282: * @return the next row in the result
283: */
284: public ExecRow getNextRowCore() throws StandardException {
285: ExecRow result = null;
286: DataValueDescriptor[] columns = null;
287:
288: beginTime = getCurrentTimeMillis();
289: if (isOpen) {
290: /* We use a do/while loop to ensure that we continue down
291: * the duplicate chain, if one exists, until we find a
292: * row that matches on all probe predicates (or the
293: * duplicate chain is exhausted.)
294: */
295: do {
296: if (firstNext) {
297: firstNext = false;
298:
299: /* Hash key could be either a single column or multiple
300: * columns. If a single column, then it is the datavalue
301: * wrapper, otherwise it is a KeyHasher.
302: */
303: Object hashEntry;
304: if (keyColumns.length == 1) {
305: hashEntry = ht.get(nextQualifiers[0][0]
306: .getOrderable());
307: } else {
308: KeyHasher mh = new KeyHasher(keyColumns.length);
309:
310: for (int index = 0; index < keyColumns.length; index++) {
311: // RESOLVE (mikem) - will need to change when we
312: // support OR's in qualifiers.
313: mh.setObject(index,
314: nextQualifiers[0][index]
315: .getOrderable());
316: }
317: hashEntry = ht.get(mh);
318: }
319:
320: if (hashEntry instanceof Vector) {
321: entryVector = (Vector) hashEntry;
322: entryVectorSize = entryVector.size();
323: columns = (DataValueDescriptor[]) entryVector
324: .firstElement();
325: } else {
326: entryVector = null;
327: entryVectorSize = 0;
328: columns = (DataValueDescriptor[]) hashEntry;
329: }
330: } else if (numFetchedOnNext < entryVectorSize) {
331: /* We walking a Vector and there's
332: * more rows left in the vector.
333: */
334: columns = (DataValueDescriptor[]) entryVector
335: .elementAt(numFetchedOnNext);
336: }
337:
338: if (columns != null) {
339: if (SanityManager.DEBUG) {
340: // Columns is really a Storable[]
341: for (int i = 0; i < columns.length; i++) {
342: if (!(columns[0] instanceof Storable)) {
343: SanityManager
344: .THROWASSERT("columns["
345: + i
346: + "] expected to be Storable, not "
347: + columns[i].getClass()
348: .getName());
349: }
350: }
351: }
352:
353: // See if the entry satisfies all of the other qualifiers
354: boolean qualifies = true;
355:
356: /* We've already "evaluated" the 1st keyColumns qualifiers
357: * when we probed into the hash table, but we need to
358: * evaluate them again here because of the behavior of
359: * NULLs. NULLs are treated as equal when building and
360: * probing the hash table so that we only get a single
361: * entry. However, NULL does not equal NULL, so the
362: * compare() method below will eliminate any row that
363: * has a key column containing a NULL.
364: */
365:
366: // RESOLVE (mikem) will have to change when qualifiers
367: // support OR's.
368: if (SanityManager.DEBUG) {
369: // we don't support 2 d qualifiers yet.
370: SanityManager
371: .ASSERT(nextQualifiers.length == 1);
372: }
373: for (int index = 0; index < nextQualifiers[0].length; index++) {
374: Qualifier q = nextQualifiers[0][index];
375:
376: qualifies = columns[q.getColumnId()].compare(q
377: .getOperator(), q.getOrderable(), q
378: .getOrderedNulls(), q.getUnknownRV());
379:
380: if (q.negateCompareResult()) {
381: qualifies = !(qualifies);
382: }
383:
384: // Stop if any predicate fails
385: if (!qualifies) {
386: break;
387: }
388: }
389:
390: if (qualifies) {
391:
392: for (int index = 0; index < columns.length; index++) {
393: nextCandidate.setColumn(index + 1,
394: columns[index]);
395: }
396:
397: result = doProjection(nextCandidate);
398: } else {
399: result = null;
400: }
401:
402: numFetchedOnNext++;
403: } else {
404: result = null;
405: }
406: } while (result == null
407: && numFetchedOnNext < entryVectorSize);
408: }
409:
410: currentRow = result;
411: setCurrentRow(result);
412:
413: nextTime += getElapsedMillis(beginTime);
414:
415: if (runTimeStatsOn) {
416: if (!isTopResultSet) {
417: /* This is simply for RunTimeStats */
418: /* We first need to get the subquery tracking array via the StatementContext */
419: StatementContext sc = activation
420: .getLanguageConnectionContext()
421: .getStatementContext();
422: subqueryTrackingArray = sc.getSubqueryTrackingArray();
423: }
424: nextTime += getElapsedMillis(beginTime);
425: }
426: return result;
427: }
428:
429: /**
430: * Return the total amount of time spent in this ResultSet
431: *
432: * @param type CURRENT_RESULTSET_ONLY - time spent only in this ResultSet
433: * ENTIRE_RESULTSET_TREE - time spent in this ResultSet and below.
434: *
435: * @return long The total amount of time spent (in milliseconds).
436: */
437: public long getTimeSpent(int type) {
438: long totTime = constructorTime + openTime + nextTime
439: + closeTime;
440:
441: if (type == CURRENT_RESULTSET_ONLY) {
442: return totTime - source.getTimeSpent(ENTIRE_RESULTSET_TREE);
443: } else {
444: return totTime;
445: }
446: }
447:
448: // ResultSet interface
449:
450: /**
451: * If the result set has been opened,
452: * close the open scan.
453: *
454: * @exception StandardException thrown on error
455: */
456: public void close() throws StandardException {
457: beginTime = getCurrentTimeMillis();
458: if (isOpen) {
459:
460: // we don't want to keep around a pointer to the
461: // row ... so it can be thrown away.
462: // REVISIT: does this need to be in a finally
463: // block, to ensure that it is executed?
464: clearCurrentRow();
465:
466: source.close();
467:
468: super .close();
469:
470: if (hashTableBuilt) {
471: // close the hash table, eating any exception
472: ht.close();
473: ht = null;
474: hashTableBuilt = false;
475: }
476: } else if (SanityManager.DEBUG)
477: SanityManager.DEBUG("CloseRepeatInfo",
478: "Close of ProjectRestrictResultSet repeated");
479:
480: closeTime += getElapsedMillis(beginTime);
481: }
482:
483: //
484: // CursorResultSet interface
485: //
486:
487: /**
488: * Gets information from its source. We might want
489: * to have this take a CursorResultSet in its constructor some day,
490: * instead of doing a cast here?
491: *
492: * @see CursorResultSet
493: *
494: * @return the row location of the current cursor row.
495: * @exception StandardException thrown on failure.
496: */
497: public RowLocation getRowLocation() throws StandardException {
498: if (SanityManager.DEBUG)
499: SanityManager.ASSERT(source instanceof CursorResultSet,
500: "source not instance of CursorResultSet");
501: return ((CursorResultSet) source).getRowLocation();
502: }
503:
504: /**
505: * Gets last row returned.
506: *
507: * @see CursorResultSet
508: *
509: * @return the last row returned.
510: * @exception StandardException thrown on failure.
511: */
512: /* RESOLVE - this should return activation.getCurrentRow(resultSetNumber),
513: * once there is such a method. (currentRow is redundant)
514: */
515: public ExecRow getCurrentRow() throws StandardException {
516: ExecRow candidateRow = null;
517: ExecRow result = null;
518: boolean restrict = false;
519: DataValueDescriptor restrictBoolean;
520:
521: if (SanityManager.DEBUG)
522: SanityManager.ASSERT(isOpen, "PRRS is expected to be open");
523:
524: /* Nothing to do if we're not currently on a row */
525: if (currentRow == null) {
526: return null;
527: }
528:
529: /* Call the child result set to get it's current row.
530: * If no row exists, then return null, else requalify it
531: * before returning.
532: */
533: candidateRow = ((CursorResultSet) source).getCurrentRow();
534: if (candidateRow != null) {
535: setCurrentRow(candidateRow);
536: /* If restriction is null, then all rows qualify */
537: restrictBoolean = (DataValueDescriptor) ((singleTableRestriction == null) ? null
538: : singleTableRestriction.invoke(activation));
539:
540: // if the result is null, we make it false --
541: // so the row won't be returned.
542: restrict = (restrictBoolean == null)
543: || ((!restrictBoolean.isNull()) && restrictBoolean
544: .getBoolean());
545: }
546:
547: if (candidateRow != null && restrict) {
548: result = doProjection(candidateRow);
549: }
550:
551: currentRow = result;
552: /* Clear the current row, if null */
553: if (result == null) {
554: clearCurrentRow();
555: }
556:
557: return currentRow;
558: }
559:
560: /**
561: * Do the projection against the source row. Use reflection
562: * where necessary, otherwise get the source column into our
563: * result row.
564: *
565: * @param sourceRow The source row.
566: *
567: * @return The result of the projection
568: *
569: * @exception StandardException thrown on failure.
570: */
571: private ExecRow doProjection(ExecRow sourceRow)
572: throws StandardException {
573: // No need to use reflection if reusing the result
574: if (reuseResult && projRow != null) {
575: return projRow;
576: }
577:
578: ExecRow result;
579:
580: // Use reflection to do as much of projection as required
581: if (projection != null) {
582: result = (ExecRow) projection.invoke(activation);
583: } else {
584: result = mappedResultRow;
585: }
586:
587: // Copy any mapped columns from the source
588: for (int index = 0; index < projectMapping.length; index++) {
589: if (projectMapping[index] != -1) {
590: result.setColumn(index + 1, sourceRow
591: .getColumn(projectMapping[index]));
592: }
593: }
594:
595: /* We need to reSet the current row after doing the projection */
596: setCurrentRow(result);
597:
598: /* Remember the result if reusing it */
599: if (reuseResult) {
600: projRow = result;
601: }
602: return result;
603: }
604:
605: // RowSource interface
606:
607: /**
608: * @see RowSource#getNextRowFromRowSource
609: * @exception StandardException on error
610: */
611: public DataValueDescriptor[] getNextRowFromRowSource()
612: throws StandardException {
613: ExecRow execRow = source.getNextRowCore();
614:
615: /* Use the single table predicates, if any,
616: * to filter out rows while populating the
617: * hash table.
618: */
619: while (execRow != null) {
620: boolean restrict = false;
621: DataValueDescriptor restrictBoolean;
622:
623: rowsSeen++;
624:
625: /* If restriction is null, then all rows qualify */
626: restrictBoolean = (DataValueDescriptor) ((singleTableRestriction == null) ? null
627: : singleTableRestriction.invoke(activation));
628:
629: // if the result is null, we make it false --
630: // so the row won't be returned.
631: restrict = (restrictBoolean == null)
632: || ((!restrictBoolean.isNull()) && restrictBoolean
633: .getBoolean());
634: if (!restrict) {
635: execRow = source.getNextRowCore();
636: continue;
637: }
638:
639: if (targetResultSet != null) {
640: /* Let the target preprocess the row. For now, this
641: * means doing an in place clone on any indexed columns
642: * to optimize cloning and so that we don't try to drain
643: * a stream multiple times. This is where we also
644: * enforce any check constraints.
645: */
646: clonedExecRow = targetResultSet
647: .preprocessSourceRow(execRow);
648: }
649:
650: /* Get a single ExecRow of the same size
651: * on the way in so that we have a row
652: * to use on the way out.
653: */
654: if (firstIntoHashtable) {
655: nextCandidate = activation.getExecutionFactory()
656: .getValueRow(execRow.nColumns());
657: firstIntoHashtable = false;
658: }
659:
660: return execRow.getRowArray();
661: }
662:
663: return null;
664: }
665:
666: /**
667: * Is this ResultSet or it's source result set for update
668: *
669: * @return Whether or not the result set is for update.
670: */
671: public boolean isForUpdate() {
672: if (source == null) {
673: return false;
674: }
675: return source.isForUpdate();
676: }
677:
678: }
|