1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.store.access.BackingStoreHashtable |
4 | |
5 | Copyright 1999, 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.iapi.store.access; |
22 | |
23 | import org.apache.derby.iapi.services.sanity.SanityManager; |
24 | |
25 | import org.apache.derby.iapi.services.io.Storable; |
26 | |
27 | import org.apache.derby.iapi.error.StandardException; |
28 | |
29 | import org.apache.derby.iapi.types.CloneableObject; |
30 | import org.apache.derby.iapi.types.DataValueDescriptor; |
31 | |
32 | import org.apache.derby.iapi.services.cache.ClassSize; |
33 | |
34 | import java.util.Enumeration; |
35 | import java.util.Hashtable; |
36 | import java.util.Properties; |
37 | import java.util.Vector; |
38 | import java.util.NoSuchElementException; |
39 | |
40 | /** |
41 | A BackingStoreHashtable is a utility class which will store a set of rows into |
42 | an in memory hash table, or overflow the hash table to a tempory on disk |
43 | structure. |
44 | <p> |
45 | All rows must contain the same number of columns, and the column at position |
46 | N of all the rows must have the same format id. If the BackingStoreHashtable needs to be |
47 | overflowed to disk, then an arbitrary row will be chosen and used as a template |
48 | for creating the underlying overflow container. |
49 | |
50 | <p> |
51 | The hash table will be built logically as follows (actual implementation |
52 | may differ). The important points are that the hash value is the standard |
53 | java hash value on the row[key_column_numbers[0], if key_column_numbers.length is 1, |
54 | or row[key_column_numbers[0, 1, ...]] if key_column_numbers.length > 1, |
55 | and that duplicate detection is done by the standard java duplicate detection provided by |
56 | java.util.Hashtable. |
57 | <p> |
58 | import java.util.Hashtable; |
59 | |
60 | hash_table = new Hashtable(); |
61 | |
62 | Object[] row; |
63 | boolean needsToClone = rowSource.needsToClone(); |
64 | |
65 | while((row = rowSource.getNextRowFromRowSource()) != null) |
66 | { |
67 | if (needsToClone) |
68 | row = clone_row_from_row(row); |
69 | |
70 | Object key = KeyHasher.buildHashKey(row, key_column_numbers); |
71 | |
72 | if ((duplicate_value = |
73 | hash_table.put(key, row)) != null) |
74 | { |
75 | Vector row_vec; |
76 | |
77 | // inserted a duplicate |
78 | if ((duplicate_value instanceof vector)) |
79 | { |
80 | row_vec = (Vector) duplicate_value; |
81 | } |
82 | else |
83 | { |
84 | // allocate vector to hold duplicates |
85 | row_vec = new Vector(2); |
86 | |
87 | // insert original row into vector |
88 | row_vec.addElement(duplicate_value); |
89 | |
90 | // put the vector as the data rather than the row |
91 | hash_table.put(key, row_vec); |
92 | } |
93 | |
94 | // insert new row into vector |
95 | row_vec.addElement(row); |
96 | } |
97 | } |
98 | |
99 | **/ |
100 | |
101 | public class BackingStoreHashtable |
102 | { |
103 | |
104 | /************************************************************************** |
105 | * Fields of the class |
106 | ************************************************************************** |
107 | */ |
108 | private TransactionController tc; |
109 | private Hashtable hash_table; |
110 | private int[] key_column_numbers; |
111 | private boolean remove_duplicates; |
112 | private boolean skipNullKeyColumns; |
113 | private Properties auxillary_runtimestats; |
114 | private RowSource row_source; |
115 | /* If max_inmemory_rowcnt > 0 then use that to decide when to spill to disk. |
116 | * Otherwise compute max_inmemory_size based on the JVM memory size when the BackingStoreHashtable |
117 | * is constructed and use that to decide when to spill to disk. |
118 | */ |
119 | private long max_inmemory_rowcnt; |
120 | private long inmemory_rowcnt; |
121 | private long max_inmemory_size; |
122 | private boolean keepAfterCommit; |
123 | |
124 | /** |
125 | * The estimated number of bytes used by Vector(0) |
126 | */ |
127 | private final static int vectorSize = ClassSize.estimateBaseFromCatalog(java.util.Vector.class); |
128 | |
129 | private DiskHashtable diskHashtable; |
130 | |
131 | /************************************************************************** |
132 | * Constructors for This class: |
133 | ************************************************************************** |
134 | */ |
135 | private BackingStoreHashtable(){} |
136 | |
137 | /** |
138 | * Create the BackingStoreHashtable from a row source. |
139 | * <p> |
140 | * This routine drains the RowSource. The performance characteristics |
141 | * depends on the number of rows inserted and the parameters to the |
142 | * constructor. |
143 | * <p> |
144 | * If the number of rows is <= "max_inmemory_rowcnt", then the rows are |
145 | * inserted into a java.util.Hashtable. In this case no |
146 | * TransactionController is necessary, a "null" tc is valid. |
147 | * <p> |
148 | * If the number of rows is > "max_inmemory_rowcnt", then the rows will |
149 | * be all placed in some sort of Access temporary file on disk. This |
150 | * case requires a valid TransactionController. |
151 | * |
152 | * @param tc An open TransactionController to be used if the |
153 | * hash table needs to overflow to disk. |
154 | * |
155 | * @param row_source RowSource to read rows from. |
156 | * |
157 | * @param key_column_numbers The column numbers of the columns in the |
158 | * scan result row to be the key to the Hashtable. |
159 | * "0" is the first column in the scan result |
160 | * row (which may be different than the first |
161 | * row in the table of the scan). |
162 | * |
163 | * @param remove_duplicates Should the Hashtable automatically remove |
164 | * duplicates, or should it create the Vector of |
165 | * duplicates? |
166 | * |
167 | * @param estimated_rowcnt The estimated number of rows in the hash table. |
168 | * Pass in -1 if there is no estimate. |
169 | * |
170 | * @param max_inmemory_rowcnt |
171 | * The maximum number of rows to insert into the |
172 | * inmemory Hash table before overflowing to disk. |
173 | * Pass in -1 if there is no maximum. |
174 | * |
175 | * @param initialCapacity If not "-1" used to initialize the java |
176 | * Hashtable. |
177 | * |
178 | * @param loadFactor If not "-1" used to initialize the java |
179 | * Hashtable. |
180 | * |
181 | * @param skipNullKeyColumns Skip rows with a null key column, if true. |
182 | * |
183 | * @param keepAfterCommit If true the hash table is kept after a commit, |
184 | * if false the hash table is dropped on the next commit. |
185 | * |
186 | * |
187 | * @exception StandardException Standard exception policy. |
188 | **/ |
189 | public BackingStoreHashtable( |
190 | TransactionController tc, |
191 | RowSource row_source, |
192 | int[] key_column_numbers, |
193 | boolean remove_duplicates, |
194 | long estimated_rowcnt, |
195 | long max_inmemory_rowcnt, |
196 | int initialCapacity, |
197 | float loadFactor, |
198 | boolean skipNullKeyColumns, |
199 | boolean keepAfterCommit) |
200 | throws StandardException |
201 | { |
202 | this.key_column_numbers = key_column_numbers; |
203 | this.remove_duplicates = remove_duplicates; |
204 | this.row_source = row_source; |
205 | this.skipNullKeyColumns = skipNullKeyColumns; |
206 | this.max_inmemory_rowcnt = max_inmemory_rowcnt; |
207 | if( max_inmemory_rowcnt > 0) |
208 | max_inmemory_size = Long.MAX_VALUE; |
209 | else |
210 | max_inmemory_size = Runtime.getRuntime().totalMemory()/100; |
211 | this.tc = tc; |
212 | this.keepAfterCommit = keepAfterCommit; |
213 | |
214 | Object[] row; |
215 | |
216 | // use passed in capacity and loadfactor if not -1, you must specify |
217 | // capacity if you want to specify loadfactor. |
218 | if (initialCapacity != -1) |
219 | { |
220 | hash_table = |
221 | ((loadFactor == -1) ? |
222 | new Hashtable(initialCapacity) : |
223 | new Hashtable(initialCapacity, loadFactor)); |
224 | } |
225 | else |
226 | { |
227 | /* We want to create the hash table based on the estimated row |
228 | * count if a) we have an estimated row count (i.e. it's greater |
229 | * than zero) and b) we think we can create a hash table to |
230 | * hold the estimated row count without running out of memory. |
231 | * The check for "b" is required because, for deeply nested |
232 | * queries and/or queries with a high number of tables in |
233 | * their FROM lists, the optimizer can end up calculating |
234 | * some very high row count estimates--even up to the point of |
235 | * Double.POSITIVE_INFINITY (see DERBY-1259 for an explanation |
236 | * of how that can happen). In that case any attempts to |
237 | * create a Hashtable of size estimated_rowcnt can cause |
238 | * OutOfMemory errors when we try to create the Hashtable. |
239 | * So as a "red flag" for that kind of situation, we check to |
240 | * see if the estimated row count is greater than the max |
241 | * in-memory size for this table. Unit-wise this comparison |
242 | * is relatively meaningless: rows vs bytes. But if our |
243 | * estimated row count is greater than the max number of |
244 | * in-memory bytes that we're allowed to consume, then |
245 | * it's very likely that creating a Hashtable with a capacity |
246 | * of estimated_rowcnt will lead to memory problems. So in |
247 | * that particular case we leave hash_table null here and |
248 | * initialize it further below, using the estimated in-memory |
249 | * size of the first row to figure out what a reasonable size |
250 | * for the Hashtable might be. |
251 | */ |
252 | hash_table = |
253 | (((estimated_rowcnt <= 0) || (row_source == null)) ? |
254 | new Hashtable() : |
255 | (estimated_rowcnt < max_inmemory_size) ? |
256 | new Hashtable((int) estimated_rowcnt) : |
257 | null); |
258 | } |
259 | |
260 | if (row_source != null) |
261 | { |
262 | boolean needsToClone = row_source.needsToClone(); |
263 | |
264 | while ((row = getNextRowFromRowSource()) != null) |
265 | { |
266 | // If we haven't initialized the hash_table yet then that's |
267 | // because a Hashtable with capacity estimated_rowcnt would |
268 | // probably cause memory problems. So look at the first row |
269 | // that we found and use that to create the hash table with |
270 | // an initial capacity such that, if it was completely full, |
271 | // it would still satisfy the max_inmemory condition. Note |
272 | // that this isn't a hard limit--the hash table can grow if |
273 | // needed. |
274 | if (hash_table == null) |
275 | { |
276 | // Check to see how much memory we think the first row |
277 | // is going to take, and then use that to set the initial |
278 | // capacity of the Hashtable. |
279 | double rowUsage = getEstimatedMemUsage(row); |
280 | hash_table = new Hashtable((int)(max_inmemory_size / rowUsage)); |
281 | } |
282 | |
283 | if (needsToClone) |
284 | { |
285 | row = cloneRow(row); |
286 | } |
287 | |
288 | Object key = |
289 | KeyHasher.buildHashKey(row, key_column_numbers); |
290 | |
291 | add_row_to_hash_table(hash_table, key, row); |
292 | } |
293 | } |
294 | |
295 | // In the (unlikely) event that we received a "red flag" estimated_rowcnt |
296 | // that is too big (see comments above), it's possible that, if row_source |
297 | // was null or else didn't have any rows, hash_table could still be null |
298 | // at this point. So we initialize it to an empty hashtable (representing |
299 | // an empty result set) so that calls to other methods on this |
300 | // BackingStoreHashtable (ex. "size()") will have a working hash_table |
301 | // on which to operate. |
302 | if (hash_table == null) |
303 | hash_table = new Hashtable(); |
304 | } |
305 | |
306 | /************************************************************************** |
307 | * Private/Protected methods of This class: |
308 | ************************************************************************** |
309 | */ |
310 | |
311 | /** |
312 | * Call method to either get next row or next row with non-null |
313 | * key columns. |
314 | * |
315 | * |
316 | * @exception StandardException Standard exception policy. |
317 | */ |
318 | private Object[] getNextRowFromRowSource() |
319 | throws StandardException |
320 | { |
321 | Object[] row = row_source.getNextRowFromRowSource(); |
322 | |
323 | if (skipNullKeyColumns) |
324 | { |
325 | while (row != null) |
326 | { |
327 | // Are any key columns null? |
328 | int index = 0; |
329 | for ( ; index < key_column_numbers.length; index++) |
330 | { |
331 | if (SanityManager.DEBUG) |
332 | { |
333 | if (! (row[key_column_numbers[index]] instanceof Storable)) |
334 | { |
335 | SanityManager.THROWASSERT( |
336 | "row[key_column_numbers[index]] expected to be Storable, not " + |
337 | row[key_column_numbers[index]].getClass().getName()); |
338 | } |
339 | } |
340 | Storable storable = (Storable) row[key_column_numbers[index]]; |
341 | if (storable.isNull()) |
342 | { |
343 | break; |
344 | } |
345 | } |
346 | // No null key columns |
347 | if (index == key_column_numbers.length) |
348 | { |
349 | return row; |
350 | } |
351 | // 1 or more null key columns |
352 | row = row_source.getNextRowFromRowSource(); |
353 | } |
354 | } |
355 | return row; |
356 | } |
357 | |
358 | /** |
359 | * Return a cloned copy of the row. |
360 | * |
361 | * @return The cloned row row to use. |
362 | * |
363 | * @exception StandardException Standard exception policy. |
364 | **/ |
365 | static Object[] cloneRow(Object[] old_row) |
366 | throws StandardException |
367 | { |
368 | Object[] new_row = new DataValueDescriptor[old_row.length]; |
369 | |
370 | // the only difference between getClone and cloneObject is cloneObject does |
371 | // not objectify a stream. We use getClone here. Beetle 4896. |
372 | for (int i = 0; i < old_row.length; i++) |
373 | { |
374 | if( old_row[i] != null) |
375 | new_row[i] = ((DataValueDescriptor) old_row[i]).getClone(); |
376 | } |
377 | |
378 | return(new_row); |
379 | } |
380 | |
381 | /** |
382 | * Do the work to add one row to the hash table. |
383 | * <p> |
384 | * |
385 | * @param row Row to add to the hash table. |
386 | * @param hash_table The java HashTable to load into. |
387 | * |
388 | * @exception StandardException Standard exception policy. |
389 | **/ |
390 | private void add_row_to_hash_table( |
391 | Hashtable hash_table, |
392 | Object key, |
393 | Object[] row) |
394 | throws StandardException |
395 | { |
396 | if( spillToDisk( hash_table, key, row)) |
397 | return; |
398 | |
399 | Object duplicate_value = null; |
400 | |
401 | if ((duplicate_value = hash_table.put(key, row)) == null) |
402 | doSpaceAccounting( row, false); |
403 | else |
404 | { |
405 | if (!remove_duplicates) |
406 | { |
407 | Vector row_vec; |
408 | |
409 | // inserted a duplicate |
410 | if ((duplicate_value instanceof Vector)) |
411 | { |
412 | doSpaceAccounting( row, false); |
413 | row_vec = (Vector) duplicate_value; |
414 | } |
415 | else |
416 | { |
417 | // allocate vector to hold duplicates |
418 | row_vec = new Vector(2); |
419 | |
420 | // insert original row into vector |
421 | row_vec.addElement(duplicate_value); |
422 | doSpaceAccounting( row, true); |
423 | } |
424 | |
425 | // insert new row into vector |
426 | row_vec.addElement(row); |
427 | |
428 | // store vector of rows back into hash table, |
429 | // overwriting the duplicate key that was |
430 | // inserted. |
431 | hash_table.put(key, row_vec); |
432 | } |
433 | } |
434 | |
435 | row = null; |
436 | } |
437 | |
438 | private void doSpaceAccounting( Object[] row, |
439 | boolean firstDuplicate) |
440 | { |
441 | inmemory_rowcnt++; |
442 | if( max_inmemory_rowcnt <= 0) |
443 | { |
444 | max_inmemory_size -= getEstimatedMemUsage(row); |
445 | if( firstDuplicate) |
446 | max_inmemory_size -= vectorSize; |
447 | } |
448 | } // end of doSpaceAccounting |
449 | |
450 | /** |
451 | * Determine whether a new row should be spilled to disk and, if so, do it. |
452 | * |
453 | * @param hash_table The in-memory hash table |
454 | * @param key The row's key |
455 | * @param row |
456 | * |
457 | * @return true if the row was spilled to disk, false if not |
458 | * |
459 | * @exception StandardException Standard exception policy. |
460 | */ |
461 | private boolean spillToDisk( Hashtable hash_table, |
462 | Object key, |
463 | Object[] row) |
464 | throws StandardException |
465 | { |
466 | // Once we have started spilling all new rows will go to disk, even if we have freed up some |
467 | // memory by moving duplicates to disk. This simplifies handling of duplicates and accounting. |
468 | if( diskHashtable == null) |
469 | { |
470 | if( max_inmemory_rowcnt > 0) |
471 | { |
472 | if( inmemory_rowcnt < max_inmemory_rowcnt) |
473 | return false; // Do not spill |
474 | } |
475 | else if( max_inmemory_size > 0) |
476 | return false; |
477 | // Want to start spilling |
478 | if( ! (row instanceof DataValueDescriptor[])) |
479 | { |
480 | if( SanityManager.DEBUG) |
481 | SanityManager.THROWASSERT( "BackingStoreHashtable row is not DataValueDescriptor[]"); |
482 | // Do not know how to put it on disk |
483 | return false; |
484 | } |
485 | diskHashtable = new DiskHashtable( tc, |
486 | (DataValueDescriptor[]) row, |
487 | key_column_numbers, |
488 | remove_duplicates, |
489 | keepAfterCommit); |
490 | } |
491 | |
492 | Object duplicateValue = hash_table.get( key); |
493 | if( duplicateValue != null) |
494 | { |
495 | if( remove_duplicates) |
496 | return true; // a degenerate case of spilling |
497 | // If we are keeping duplicates then move all the duplicates from memory to disk |
498 | // This simplifies finding duplicates: they are either all in memory or all on disk. |
499 | if( duplicateValue instanceof Vector) |
500 | { |
501 | Vector duplicateVec = (Vector) duplicateValue; |
502 | for( int i = duplicateVec.size() - 1; i >= 0; i--) |
503 | { |
504 | Object[] dupRow = (Object[]) duplicateVec.elementAt(i); |
505 | diskHashtable.put( key, dupRow); |
506 | } |
507 | } |
508 | else |
509 | diskHashtable.put( key, (Object []) duplicateValue); |
510 | hash_table.remove( key); |
511 | } |
512 | diskHashtable.put( key, row); |
513 | return true; |
514 | } // end of spillToDisk |
515 | |
516 | /** |
517 | * Take a row and return an estimate as to how much memory that |
518 | * row will consume. |
519 | * |
520 | * @param row The row for which we want to know the memory usage. |
521 | * @return A guess as to how much memory the current row will |
522 | * use. |
523 | */ |
524 | private long getEstimatedMemUsage(Object [] row) |
525 | { |
526 | long rowMem = 0; |
527 | for( int i = 0; i < row.length; i++) |
528 | { |
529 | if (row[i] instanceof DataValueDescriptor) |
530 | rowMem += ((DataValueDescriptor) row[i]).estimateMemoryUsage(); |
531 | rowMem += ClassSize.refSize; |
532 | } |
533 | |
534 | rowMem += ClassSize.refSize; |
535 | return rowMem; |
536 | } |
537 | |
538 | /************************************************************************** |
539 | * Public Methods of This class: |
540 | ************************************************************************** |
541 | */ |
542 | |
543 | /** |
544 | * Close the BackingStoreHashtable. |
545 | * <p> |
546 | * Perform any necessary cleanup after finishing with the hashtable. Will |
547 | * deallocate/dereference objects as necessary. If the table has gone |
548 | * to disk this will drop any on disk files used to support the hash table. |
549 | * <p> |
550 | * |
551 | * @exception StandardException Standard exception policy. |
552 | **/ |
553 | public void close() |
554 | throws StandardException |
555 | { |
556 | hash_table = null; |
557 | if( diskHashtable != null) |
558 | { |
559 | diskHashtable.close(); |
560 | diskHashtable = null; |
561 | } |
562 | return; |
563 | } |
564 | |
565 | /** |
566 | * Return an Enumeration that can be used to scan entire table. |
567 | * <p> |
568 | * RESOLVE - is it worth it to support this routine when we have a |
569 | * disk overflow hash table? |
570 | * |
571 | * @return The Enumeration. |
572 | * |
573 | * @exception StandardException Standard exception policy. |
574 | **/ |
575 | public Enumeration elements() |
576 | throws StandardException |
577 | { |
578 | if( diskHashtable == null) |
579 | return(hash_table.elements()); |
580 | return new BackingStoreHashtableEnumeration(); |
581 | } |
582 | |
583 | /** |
584 | * get data associated with given key. |
585 | * <p> |
586 | * There are 2 different types of objects returned from this routine. |
587 | * <p> |
588 | * In both cases, the key value is either the object stored in |
589 | * row[key_column_numbers[0]], if key_column_numbers.length is 1, |
590 | * otherwise it is a KeyHasher containing |
591 | * the objects stored in row[key_column_numbers[0, 1, ...]]. |
592 | * For every qualifying unique row value an entry is placed into the |
593 | * Hashtable. |
594 | * <p> |
595 | * For row values with duplicates, the value of the data is a Vector of |
596 | * rows. |
597 | * <p> |
598 | * The caller will have to call "instanceof" on the data value |
599 | * object if duplicates are expected, to determine if the data value |
600 | * of the Hashtable entry is a row or is a Vector of rows. |
601 | * <p> |
602 | * The BackingStoreHashtable "owns" the objects returned from the get() |
603 | * routine. They remain valid until the next access to the |
604 | * BackingStoreHashtable. If the client needs to keep references to these |
605 | * objects, it should clone copies of the objects. A valid |
606 | * BackingStoreHashtable can place all rows into a disk based conglomerate, |
607 | * declare a row buffer and then reuse that row buffer for every get() |
608 | * call. |
609 | * |
610 | * @return The value to which the key is mapped in this hashtable; |
611 | * null if the key is not mapped to any value in this hashtable. |
612 | * |
613 | * @param key The key to hash on. |
614 | * |
615 | * @exception StandardException Standard exception policy. |
616 | **/ |
617 | public Object get(Object key) |
618 | throws StandardException |
619 | { |
620 | Object obj = hash_table.get(key); |
621 | if( diskHashtable == null || obj != null) |
622 | return obj; |
623 | return diskHashtable.get( key); |
624 | } |
625 | |
626 | /** |
627 | * Return runtime stats to caller by adding them to prop. |
628 | * <p> |
629 | * |
630 | * @param prop The set of properties to append to. |
631 | * |
632 | * @exception StandardException Standard exception policy. |
633 | **/ |
634 | public void getAllRuntimeStats(Properties prop) |
635 | throws StandardException |
636 | { |
637 | if (auxillary_runtimestats != null) |
638 | org.apache.derby.iapi.util.PropertyUtil.copyProperties(auxillary_runtimestats, prop); |
639 | } |
640 | |
641 | /** |
642 | * remove a row from the hash table. |
643 | * <p> |
644 | * a remove of a duplicate removes the entire duplicate list. |
645 | * |
646 | * @param key The key of the row to remove. |
647 | * |
648 | * @exception StandardException Standard exception policy. |
649 | **/ |
650 | public Object remove( |
651 | Object key) |
652 | throws StandardException |
653 | { |
654 | Object obj = hash_table.remove(key); |
655 | if( obj != null || diskHashtable == null) |
656 | return obj; |
657 | return diskHashtable.remove(key); |
658 | } |
659 | |
660 | /** |
661 | * Set the auxillary runtime stats. |
662 | * <p> |
663 | * getRuntimeStats() will return both the auxillary stats and any |
664 | * BackingStoreHashtable() specific stats. Note that each call to |
665 | * setAuxillaryRuntimeStats() overwrites the Property set that was |
666 | * set previously. |
667 | * |
668 | * @param prop The set of properties to append from. |
669 | * |
670 | * @exception StandardException Standard exception policy. |
671 | **/ |
672 | public void setAuxillaryRuntimeStats(Properties prop) |
673 | throws StandardException |
674 | { |
675 | auxillary_runtimestats = prop; |
676 | } |
677 | |
678 | /** |
679 | * Put a row into the hash table. |
680 | * <p> |
681 | * The in memory hash table will need to keep a reference to the row |
682 | * after the put call has returned. If "needsToClone" is true then the |
683 | * hash table will make a copy of the row and put that, else if |
684 | * "needsToClone" is false then the hash table will keep a reference to |
685 | * the row passed in and no copy will be made. |
686 | * <p> |
687 | * If rouine returns false, then no reference is kept to the duplicate |
688 | * row which was rejected (thus allowing caller to reuse the object). |
689 | * |
690 | * @param needsToClone does this routine have to make a copy of the row, |
691 | * in order to keep a reference to it after return? |
692 | * @param row The row to insert into the table. |
693 | * |
694 | * @return true if row was inserted into the hash table. Returns |
695 | * false if the BackingStoreHashtable is eliminating |
696 | * duplicates, and the row being inserted is a duplicate, |
697 | * or if we are skipping rows with 1 or more null key columns |
698 | * and we find a null key column. |
699 | * |
700 | * @exception StandardException Standard exception policy. |
701 | **/ |
702 | public boolean put( |
703 | boolean needsToClone, |
704 | Object[] row) |
705 | throws StandardException |
706 | { |
707 | // Are any key columns null? |
708 | if (skipNullKeyColumns) |
709 | { |
710 | int index = 0; |
711 | for ( ; index < key_column_numbers.length; index++) |
712 | { |
713 | if (SanityManager.DEBUG) |
714 | { |
715 | if (! (row[key_column_numbers[index]] instanceof Storable)) |
716 | { |
717 | SanityManager.THROWASSERT( |
718 | "row[key_column_numbers[index]] expected to be Storable, not " + |
719 | row[key_column_numbers[index]].getClass().getName()); |
720 | } |
721 | } |
722 | Storable storable = (Storable) row[key_column_numbers[index]]; |
723 | if (storable.isNull()) |
724 | { |
725 | return false; |
726 | } |
727 | } |
728 | } |
729 | |
730 | if (needsToClone) |
731 | { |
732 | row = cloneRow(row); |
733 | } |
734 | |
735 | Object key = KeyHasher.buildHashKey(row, key_column_numbers); |
736 | |
737 | if ((remove_duplicates) && (get(key) != null)) |
738 | { |
739 | return(false); |
740 | } |
741 | else |
742 | { |
743 | add_row_to_hash_table(hash_table, key, row); |
744 | return(true); |
745 | } |
746 | } |
747 | |
748 | /** |
749 | * Return number of unique rows in the hash table. |
750 | * <p> |
751 | * |
752 | * @return The number of unique rows in the hash table. |
753 | * |
754 | * @exception StandardException Standard exception policy. |
755 | **/ |
756 | public int size() |
757 | throws StandardException |
758 | { |
759 | if( diskHashtable == null) |
760 | return(hash_table.size()); |
761 | return hash_table.size() + diskHashtable.size(); |
762 | } |
763 | |
764 | private class BackingStoreHashtableEnumeration implements Enumeration |
765 | { |
766 | private Enumeration memoryEnumeration; |
767 | private Enumeration diskEnumeration; |
768 | |
769 | BackingStoreHashtableEnumeration() |
770 | { |
771 | memoryEnumeration = hash_table.elements(); |
772 | if( diskHashtable != null) |
773 | { |
774 | try |
775 | { |
776 | diskEnumeration = diskHashtable.elements(); |
777 | } |
778 | catch( StandardException se) |
779 | { |
780 | diskEnumeration = null; |
781 | } |
782 | } |
783 | } |
784 | |
785 | public boolean hasMoreElements() |
786 | { |
787 | if( memoryEnumeration != null) |
788 | { |
789 | if( memoryEnumeration.hasMoreElements()) |
790 | return true; |
791 | memoryEnumeration = null; |
792 | } |
793 | if( diskEnumeration == null) |
794 | return false; |
795 | return diskEnumeration.hasMoreElements(); |
796 | } |
797 | |
798 | public Object nextElement() throws NoSuchElementException |
799 | { |
800 | if( memoryEnumeration != null) |
801 | { |
802 | if( memoryEnumeration.hasMoreElements()) |
803 | return memoryEnumeration.nextElement(); |
804 | memoryEnumeration = null; |
805 | } |
806 | return diskEnumeration.nextElement(); |
807 | } |
808 | } // end of class BackingStoreHashtableEnumeration |
809 | } |