1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.store.access.DiskHashtable |
4 | |
5 | Copyright 2005 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 java.util.Enumeration; |
24 | import java.util.NoSuchElementException; |
25 | import java.util.Properties; |
26 | import java.util.Vector; |
27 | import org.apache.derby.iapi.error.StandardException; |
28 | import org.apache.derby.iapi.services.io.FormatableBitSet; |
29 | import org.apache.derby.iapi.types.DataValueDescriptor; |
30 | import org.apache.derby.iapi.types.SQLInteger; |
31 | import org.apache.derby.impl.store.access.heap.HeapRowLocation; |
32 | import org.apache.derby.iapi.types.RowLocation; |
33 | import org.apache.derby.iapi.services.context.ContextService; |
34 | import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
35 | |
36 | /** |
37 | * This class is used by BackingStoreHashtable when the BackingStoreHashtable must spill to disk. |
38 | * It implements the methods of a hash table: put, get, remove, elements, however it is not implemented |
39 | * as a hash table. In order to minimize the amount of unique code it is implemented using a Btree and a heap |
40 | * conglomerate. The Btree indexes the hash code of the row key. The actual key may be too long for |
41 | * our Btree implementation. |
42 | * |
43 | * Created: Fri Jan 28 13:58:03 2005 |
44 | * |
45 | * @author <a href="mailto:klebanof@us.ibm.com">Jack Klebanoff</a> |
46 | * @version 1.0 |
47 | */ |
48 | |
49 | public class DiskHashtable |
50 | { |
51 | private final long rowConglomerateId; |
52 | private ConglomerateController rowConglomerate; |
53 | private final long btreeConglomerateId; |
54 | private ConglomerateController btreeConglomerate; |
55 | private final DataValueDescriptor[] btreeRow; |
56 | private final int[] key_column_numbers; |
57 | private final boolean remove_duplicates; |
58 | private final TransactionController tc; |
59 | private final DataValueDescriptor[] row; |
60 | private final DataValueDescriptor[] scanKey = { new SQLInteger()}; |
61 | private int size; |
62 | private boolean keepStatistics; |
63 | |
64 | /** |
65 | * Creates a new <code>DiskHashtable</code> instance. |
66 | * |
67 | * @param tc |
68 | * @param template An array of DataValueDescriptors that serves as a template for the rows. |
69 | * @param key_column_numbers The indexes of the key columns (0 based) |
70 | * @param remove_duplicates If true then rows with duplicate keys are removed |
71 | * @param keepAfterCommit If true then the hash table is kept after a commit |
72 | */ |
73 | public DiskHashtable( TransactionController tc, |
74 | DataValueDescriptor[] template, |
75 | int[] key_column_numbers, |
76 | boolean remove_duplicates, |
77 | boolean keepAfterCommit) |
78 | throws StandardException |
79 | { |
80 | this.tc = tc; |
81 | this.key_column_numbers = key_column_numbers; |
82 | this.remove_duplicates = remove_duplicates; |
83 | LanguageConnectionContext lcc = (LanguageConnectionContext) |
84 | ContextService.getContextOrNull(LanguageConnectionContext.CONTEXT_ID); |
85 | keepStatistics = (lcc != null) && lcc.getRunTimeStatisticsMode(); |
86 | row = new DataValueDescriptor[ template.length]; |
87 | for( int i = 0; i < row.length; i++) |
88 | row[i] = template[i].getNewNull(); |
89 | int tempFlags = keepAfterCommit ? (TransactionController.IS_TEMPORARY | TransactionController.IS_KEPT) |
90 | : TransactionController.IS_TEMPORARY; |
91 | |
92 | rowConglomerateId = tc.createConglomerate( "heap", |
93 | template, |
94 | (ColumnOrdering[]) null, |
95 | (Properties) null, |
96 | tempFlags); |
97 | rowConglomerate = tc.openConglomerate( rowConglomerateId, |
98 | keepAfterCommit, |
99 | TransactionController.OPENMODE_FORUPDATE, |
100 | TransactionController.MODE_TABLE, |
101 | TransactionController.ISOLATION_NOLOCK /* Single thread only */ ); |
102 | |
103 | btreeRow = new DataValueDescriptor[] { new SQLInteger(), rowConglomerate.newRowLocationTemplate()}; |
104 | Properties btreeProps = new Properties(); |
105 | btreeProps.put( "baseConglomerateId", String.valueOf( rowConglomerateId)); |
106 | btreeProps.put( "rowLocationColumn", "1"); |
107 | btreeProps.put( "allowDuplicates", "false"); // Because the row location is part of the key |
108 | btreeProps.put( "nKeyFields", "2"); // Include the row location column |
109 | btreeProps.put( "nUniqueColumns", "2"); // Include the row location column |
110 | btreeProps.put( "maintainParentLinks", "false"); |
111 | btreeConglomerateId = tc.createConglomerate( "BTREE", |
112 | btreeRow, |
113 | (ColumnOrdering[]) null, |
114 | btreeProps, |
115 | tempFlags); |
116 | |
117 | btreeConglomerate = tc.openConglomerate( btreeConglomerateId, |
118 | keepAfterCommit, |
119 | TransactionController.OPENMODE_FORUPDATE, |
120 | TransactionController.MODE_TABLE, |
121 | TransactionController.ISOLATION_NOLOCK /* Single thread only */ ); |
122 | } // end of constructor |
123 | |
124 | public void close() throws StandardException |
125 | { |
126 | btreeConglomerate.close(); |
127 | rowConglomerate.close(); |
128 | tc.dropConglomerate( btreeConglomerateId); |
129 | tc.dropConglomerate( rowConglomerateId); |
130 | } // end of close |
131 | |
132 | /** |
133 | * Put a new row in the overflow structure. |
134 | * |
135 | * @param row The row to be inserted. |
136 | * |
137 | * @return true if the row was added, |
138 | * false if it was not added (because it was a duplicate and we are eliminating duplicates). |
139 | * |
140 | * @exception StandardException standard error policy |
141 | */ |
142 | public boolean put( Object key, Object[] row) |
143 | throws StandardException |
144 | { |
145 | boolean isDuplicate = false; |
146 | if( remove_duplicates || keepStatistics) |
147 | { |
148 | // Go to the work of finding out whether it is a duplicate |
149 | isDuplicate = (getRemove( key, false, true) != null); |
150 | if( remove_duplicates && isDuplicate) |
151 | return false; |
152 | } |
153 | rowConglomerate.insertAndFetchLocation( (DataValueDescriptor[]) row, (RowLocation) btreeRow[1]); |
154 | btreeRow[0].setValue( key.hashCode()); |
155 | btreeConglomerate.insert( btreeRow); |
156 | if( keepStatistics && !isDuplicate) |
157 | size++; |
158 | return true; |
159 | } // end of put |
160 | |
161 | /** |
162 | * Get a row from the overflow structure. |
163 | * |
164 | * @param key If the rows only have one key column then the key value. If there is more than one |
165 | * key column then a KeyHasher |
166 | * |
167 | * @return null if there is no corresponding row, |
168 | * the row (DataValueDescriptor[]) if there is exactly one row with the key |
169 | * a Vector of all the rows with the key if there is more than one. |
170 | * |
171 | * @exception StandardException |
172 | */ |
173 | public Object get( Object key) |
174 | throws StandardException |
175 | { |
176 | return getRemove( key, false, false); |
177 | } |
178 | |
179 | private Object getRemove( Object key, boolean remove, boolean existenceOnly) |
180 | throws StandardException |
181 | { |
182 | int hashCode = key.hashCode(); |
183 | int rowCount = 0; |
184 | Object retValue = null; |
185 | |
186 | scanKey[0].setValue( hashCode); |
187 | ScanController scan = tc.openScan( btreeConglomerateId, |
188 | false, // do not hold |
189 | remove ? TransactionController.OPENMODE_FORUPDATE : 0, |
190 | TransactionController.MODE_TABLE, |
191 | TransactionController.ISOLATION_READ_UNCOMMITTED, |
192 | null, // Scan all the columns |
193 | scanKey, |
194 | ScanController.GE, |
195 | (Qualifier[][]) null, |
196 | scanKey, |
197 | ScanController.GT); |
198 | try |
199 | { |
200 | while( scan.fetchNext( btreeRow)) |
201 | { |
202 | if( rowConglomerate.fetch( (RowLocation) btreeRow[1], row, (FormatableBitSet) null /* all columns */) |
203 | && rowMatches( row, key)) |
204 | { |
205 | if( existenceOnly) |
206 | return this; |
207 | |
208 | rowCount++; |
209 | if( rowCount == 1) |
210 | retValue = BackingStoreHashtable.cloneRow( row); |
211 | else |
212 | { |
213 | Vector v; |
214 | if( rowCount == 2) |
215 | { |
216 | v = new Vector( 2); |
217 | v.add( retValue); |
218 | retValue = v; |
219 | } |
220 | else |
221 | v = (Vector) retValue; |
222 | v.add( BackingStoreHashtable.cloneRow( row)); |
223 | } |
224 | if( remove) |
225 | { |
226 | rowConglomerate.delete( (RowLocation) btreeRow[1]); |
227 | scan.delete(); |
228 | size--; |
229 | } |
230 | if( remove_duplicates) |
231 | // This must be the only row with the key |
232 | return retValue; |
233 | } |
234 | } |
235 | } |
236 | finally |
237 | { |
238 | scan.close(); |
239 | } |
240 | return retValue; |
241 | } // end of getRemove |
242 | |
243 | |
244 | private boolean rowMatches( DataValueDescriptor[] row, |
245 | Object key) |
246 | { |
247 | if( key_column_numbers.length == 1) |
248 | return row[ key_column_numbers[0]].equals( key); |
249 | |
250 | KeyHasher kh = (KeyHasher) key; |
251 | for( int i = 0; i < key_column_numbers.length; i++) |
252 | { |
253 | if( ! row[ key_column_numbers[i]].equals( kh.getObject(i))) |
254 | return false; |
255 | } |
256 | return true; |
257 | } // end of rowMatches |
258 | |
259 | /** |
260 | * remove all rows with a given key from the hash table. |
261 | * |
262 | * @param key The key of the rows to remove. |
263 | * |
264 | * @return The removed row(s). |
265 | * |
266 | * @exception StandardException Standard exception policy. |
267 | **/ |
268 | public Object remove( Object key) |
269 | throws StandardException |
270 | { |
271 | return getRemove( key, true, false); |
272 | } // end of remove |
273 | |
274 | /** |
275 | * @return The number of rows in the hash table |
276 | */ |
277 | public int size() |
278 | { |
279 | return size; |
280 | } |
281 | |
282 | /** |
283 | * Return an Enumeration that can be used to scan entire table. |
284 | * <p> |
285 | * RESOLVE - is it worth it to support this routine? |
286 | * |
287 | * @return The Enumeration. |
288 | * |
289 | * @exception StandardException Standard exception policy. |
290 | **/ |
291 | public Enumeration elements() |
292 | throws StandardException |
293 | { |
294 | return new ElementEnum(); |
295 | } |
296 | |
297 | private class ElementEnum implements Enumeration |
298 | { |
299 | private ScanController scan; |
300 | private boolean hasMore; |
301 | |
302 | ElementEnum() |
303 | { |
304 | try |
305 | { |
306 | scan = tc.openScan( rowConglomerateId, |
307 | false, // do not hold |
308 | 0, // read only |
309 | TransactionController.MODE_TABLE, |
310 | TransactionController.ISOLATION_NOLOCK, |
311 | (FormatableBitSet) null, // all columns |
312 | (DataValueDescriptor[]) null, // no start key |
313 | 0, // no start key operator |
314 | (Qualifier[][]) null, |
315 | (DataValueDescriptor[]) null, // no stop key |
316 | 0 /* no stop key operator */); |
317 | hasMore = scan.next(); |
318 | if( ! hasMore) |
319 | { |
320 | scan.close(); |
321 | scan = null; |
322 | } |
323 | } |
324 | catch( StandardException se) |
325 | { |
326 | hasMore = false; |
327 | if( scan != null) |
328 | { |
329 | try |
330 | { |
331 | scan.close(); |
332 | } |
333 | catch( StandardException se1){}; |
334 | scan = null; |
335 | } |
336 | } |
337 | } // end of constructor |
338 | |
339 | public boolean hasMoreElements() |
340 | { |
341 | return hasMore; |
342 | } |
343 | |
344 | public Object nextElement() |
345 | { |
346 | if( ! hasMore) |
347 | throw new NoSuchElementException(); |
348 | try |
349 | { |
350 | scan.fetch( row); |
351 | Object retValue = BackingStoreHashtable.cloneRow( row); |
352 | hasMore = scan.next(); |
353 | if( ! hasMore) |
354 | { |
355 | scan.close(); |
356 | scan = null; |
357 | } |
358 | |
359 | return retValue; |
360 | } |
361 | catch( StandardException se) |
362 | { |
363 | if( scan != null) |
364 | { |
365 | try |
366 | { |
367 | scan.close(); |
368 | } |
369 | catch( StandardException se1){}; |
370 | scan = null; |
371 | } |
372 | throw new NoSuchElementException(); |
373 | } |
374 | } // end of nextElement |
375 | } // end of class ElementEnum |
376 | } |