1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 /**
20 * JDBM LICENSE v1.00
21 *
22 * Redistribution and use of this software and associated documentation
23 * ("Software"), with or without modification, are permitted provided
24 * that the following conditions are met:
25 *
26 * 1. Redistributions of source code must retain copyright
27 * statements and notices. Redistributions must also contain a
28 * copy of this document.
29 *
30 * 2. Redistributions in binary form must reproduce the
31 * above copyright notice, this list of conditions and the
32 * following disclaimer in the documentation and/or other
33 * materials provided with the distribution.
34 *
35 * 3. The name "JDBM" must not be used to endorse or promote
36 * products derived from this Software without prior written
37 * permission of Cees de Groot. For written permission,
38 * please contact cg@cdegroot.com.
39 *
40 * 4. Products derived from this Software may not be called "JDBM"
41 * nor may "JDBM" appear in their names without prior written
42 * permission of Cees de Groot.
43 *
44 * 5. Due credit should be given to the JDBM Project
45 * (http://jdbm.sourceforge.net/).
46 *
47 * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
48 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
49 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
50 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
51 * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
52 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
53 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
54 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
56 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
57 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
58 * OF THE POSSIBILITY OF SUCH DAMAGE.
59 *
60 * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
61 * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
62 * Contributions are Copyright (C) 2000 by their associated contributors.
63 *
64 * $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
65 */
66
67 package org.apache.hadoop.hive.ql.util.jdbm.recman;
68
69 import java.io.IOException;
70 import java.io.File;
71
72 import java.util.HashMap;
73 import java.util.Map;
74
75 import org.apache.hadoop.hive.ql.util.jdbm.RecordManager;
76 import org.apache.hadoop.hive.ql.util.jdbm.helper.Serializer;
77 import org.apache.hadoop.hive.ql.util.jdbm.helper.DefaultSerializer;
78
79 /**
80 * This class manages records, which are uninterpreted blobs of data. The
81 * set of operations is simple and straightforward: you communicate with
82 * the class using long "rowids" and byte[] data blocks. Rowids are returned
83 * on inserts and you can stash them away someplace safe to be able to get
84 * back to them. Data blocks can be as long as you wish, and may have
85 * lengths different from the original when updating.
86 * <p>
87 * Operations are synchronized, so that only one of them will happen
88 * concurrently even if you hammer away from multiple threads. Operations
89 * are made atomic by keeping a transaction log which is recovered after
90 * a crash, so the operations specified by this interface all have ACID
91 * properties.
92 * <p>
93 * You identify a file by just the name. The package attaches <tt>.db</tt>
94 * for the database file, and <tt>.lg</tt> for the transaction log. The
95 * transaction log is synchronized regularly and then restarted, so don't
96 * worry if you see the size going up and down.
97 *
98 * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
99 * @author <a href="cg@cdegroot.com">Cees de Groot</a>
100 * @version $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
101 */
102 public final class BaseRecordManager
103 implements RecordManager
104 {
105
106 /**
107 * Underlying record file.
108 */
109 private RecordFile _file;
110
111
112 /**
113 * Physical row identifier manager.
114 */
115 private PhysicalRowIdManager _physMgr;
116
117
118 /**
119 * Logigal to Physical row identifier manager.
120 */
121 private LogicalRowIdManager _logMgr;
122
123
124 /**
125 * Page manager.
126 */
127 private PageManager _pageman;
128
129
130 /**
131 * Reserved slot for name directory.
132 */
133 public static final int NAME_DIRECTORY_ROOT = 0;
134
135
136 /**
137 * Static debugging flag
138 */
139 public static final boolean DEBUG = false;
140
141
142 /**
143 * Directory of named JDBMHashtables. This directory is a persistent
144 * directory, stored as a Hashtable. It can be retrived by using
145 * the NAME_DIRECTORY_ROOT.
146 */
147 private Map _nameDirectory;
148
149
150 /**
151 * Creates a record manager for the indicated file
152 *
153 * @throws IOException when the file cannot be opened or is not
154 * a valid file content-wise.
155 */
156 public BaseRecordManager( String filename )
157 throws IOException
158 {
159 _file = new RecordFile( filename );
160 _pageman = new PageManager( _file );
161 _physMgr = new PhysicalRowIdManager( _file, _pageman );
162 _logMgr = new LogicalRowIdManager( _file, _pageman );
163 }
164
165 /**
166 * Creates a record manager for the indicated file
167 *
168 * @throws IOException when the file cannot be opened or is not
169 * a valid file content-wise.
170 */
171 public BaseRecordManager( File file )
172 throws IOException
173 {
174 _file = new RecordFile( file );
175 _pageman = new PageManager( _file );
176 _physMgr = new PhysicalRowIdManager( _file, _pageman );
177 _logMgr = new LogicalRowIdManager( _file, _pageman );
178 }
179
180
181
182
183 /**
184 * Get the underlying Transaction Manager
185 */
186 public synchronized TransactionManager getTransactionManager()
187 {
188 checkIfClosed();
189
190 return _file.txnMgr;
191 }
192
193
194 /**
195 * Switches off transactioning for the record manager. This means
196 * that a) a transaction log is not kept, and b) writes aren't
197 * synch'ed after every update. This is useful when batch inserting
198 * into a new database.
199 * <p>
200 * Only call this method directly after opening the file, otherwise
201 * the results will be undefined.
202 */
203 public synchronized void disableTransactions()
204 {
205 checkIfClosed();
206
207 _file.disableTransactions();
208 }
209
210
211 /**
212 * Closes the record manager.
213 *
214 * @throws IOException when one of the underlying I/O operations fails.
215 */
216 public synchronized void close()
217 throws IOException
218 {
219 checkIfClosed();
220
221 _pageman.close();
222 _pageman = null;
223
224 _file.close();
225 _file = null;
226 }
227
228
229 /**
230 * Inserts a new record using standard java object serialization.
231 *
232 * @param obj the object for the new record.
233 * @return the rowid for the new record.
234 * @throws IOException when one of the underlying I/O operations fails.
235 */
236 public long insert( Object obj )
237 throws IOException
238 {
239 return insert( obj, DefaultSerializer.INSTANCE );
240 }
241
242
243 /**
244 * Inserts a new record using a custom serializer.
245 *
246 * @param obj the object for the new record.
247 * @param serializer a custom serializer
248 * @return the rowid for the new record.
249 * @throws IOException when one of the underlying I/O operations fails.
250 */
251 public synchronized long insert( Object obj, Serializer serializer )
252 throws IOException
253 {
254 byte[] data;
255 long recid;
256 Location physRowId;
257
258 checkIfClosed();
259
260 data = serializer.serialize( obj );
261 physRowId = _physMgr.insert( data, 0, data.length );
262 recid = _logMgr.insert( physRowId ).toLong();
263 if ( DEBUG ) {
264 System.out.println( "BaseRecordManager.insert() recid " + recid + " length " + data.length ) ;
265 }
266 return recid;
267 }
268
269 /**
270 * Deletes a record.
271 *
272 * @param recid the rowid for the record that should be deleted.
273 * @throws IOException when one of the underlying I/O operations fails.
274 */
275 public synchronized void delete( long recid )
276 throws IOException
277 {
278 checkIfClosed();
279 if ( recid <= 0 ) {
280 throw new IllegalArgumentException( "Argument 'recid' is invalid: "
281 + recid );
282 }
283
284 if ( DEBUG ) {
285 System.out.println( "BaseRecordManager.delete() recid " + recid ) ;
286 }
287
288 Location logRowId = new Location( recid );
289 Location physRowId = _logMgr.fetch( logRowId );
290 _physMgr.delete( physRowId );
291 _logMgr.delete( logRowId );
292 }
293
294
295 /**
296 * Updates a record using standard java object serialization.
297 *
298 * @param recid the recid for the record that is to be updated.
299 * @param obj the new object for the record.
300 * @throws IOException when one of the underlying I/O operations fails.
301 */
302 public void update( long recid, Object obj )
303 throws IOException
304 {
305 update( recid, obj, DefaultSerializer.INSTANCE );
306 }
307
308
309 /**
310 * Updates a record using a custom serializer.
311 *
312 * @param recid the recid for the record that is to be updated.
313 * @param obj the new object for the record.
314 * @param serializer a custom serializer
315 * @throws IOException when one of the underlying I/O operations fails.
316 */
317 public synchronized void update( long recid, Object obj, Serializer serializer )
318 throws IOException
319 {
320 checkIfClosed();
321 if ( recid <= 0 ) {
322 throw new IllegalArgumentException( "Argument 'recid' is invalid: "
323 + recid );
324 }
325
326 Location logRecid = new Location( recid );
327 Location physRecid = _logMgr.fetch( logRecid );
328
329 byte[] data = serializer.serialize( obj );
330 if ( DEBUG ) {
331 System.out.println( "BaseRecordManager.update() recid " + recid + " length " + data.length ) ;
332 }
333
334 Location newRecid = _physMgr.update( physRecid, data, 0, data.length );
335 if ( ! newRecid.equals( physRecid ) ) {
336 _logMgr.update( logRecid, newRecid );
337 }
338 }
339
340
341 /**
342 * Fetches a record using standard java object serialization.
343 *
344 * @param recid the recid for the record that must be fetched.
345 * @return the object contained in the record.
346 * @throws IOException when one of the underlying I/O operations fails.
347 */
348 public Object fetch( long recid )
349 throws IOException
350 {
351 return fetch( recid, DefaultSerializer.INSTANCE );
352 }
353
354
355 /**
356 * Fetches a record using a custom serializer.
357 *
358 * @param recid the recid for the record that must be fetched.
359 * @param serializer a custom serializer
360 * @return the object contained in the record.
361 * @throws IOException when one of the underlying I/O operations fails.
362 */
363 public synchronized Object fetch( long recid, Serializer serializer )
364 throws IOException
365 {
366 byte[] data;
367
368 checkIfClosed();
369 if ( recid <= 0 ) {
370 throw new IllegalArgumentException( "Argument 'recid' is invalid: "
371 + recid );
372 }
373 data = _physMgr.fetch( _logMgr.fetch( new Location( recid ) ) );
374 if ( DEBUG ) {
375 System.out.println( "BaseRecordManager.fetch() recid " + recid + " length " + data.length ) ;
376 }
377 return serializer.deserialize( data );
378 }
379
380
381 /**
382 * Returns the number of slots available for "root" rowids. These slots
383 * can be used to store special rowids, like rowids that point to
384 * other rowids. Root rowids are useful for bootstrapping access to
385 * a set of data.
386 */
387 public int getRootCount()
388 {
389 return FileHeader.NROOTS;
390 }
391
392 /**
393 * Returns the indicated root rowid.
394 *
395 * @see #getRootCount
396 */
397 public synchronized long getRoot( int id )
398 throws IOException
399 {
400 checkIfClosed();
401
402 return _pageman.getFileHeader().getRoot( id );
403 }
404
405
406 /**
407 * Sets the indicated root rowid.
408 *
409 * @see #getRootCount
410 */
411 public synchronized void setRoot( int id, long rowid )
412 throws IOException
413 {
414 checkIfClosed();
415
416 _pageman.getFileHeader().setRoot( id, rowid );
417 }
418
419
420 /**
421 * Obtain the record id of a named object. Returns 0 if named object
422 * doesn't exist.
423 */
424 public long getNamedObject( String name )
425 throws IOException
426 {
427 checkIfClosed();
428
429 Map nameDirectory = getNameDirectory();
430 Long recid = (Long) nameDirectory.get( name );
431 if ( recid == null ) {
432 return 0;
433 }
434 return recid.longValue();
435 }
436
437 /**
438 * Set the record id of a named object.
439 */
440 public void setNamedObject( String name, long recid )
441 throws IOException
442 {
443 checkIfClosed();
444
445 Map nameDirectory = getNameDirectory();
446 if ( recid == 0 ) {
447 // remove from hashtable
448 nameDirectory.remove( name );
449 } else {
450 nameDirectory.put( name, new Long( recid ) );
451 }
452 saveNameDirectory( nameDirectory );
453 }
454
455
456 /**
457 * Commit (make persistent) all changes since beginning of transaction.
458 */
459 public synchronized void commit()
460 throws IOException
461 {
462 checkIfClosed();
463
464 _pageman.commit();
465 }
466
467
468 /**
469 * Rollback (cancel) all changes since beginning of transaction.
470 */
471 public synchronized void rollback()
472 throws IOException
473 {
474 checkIfClosed();
475
476 _pageman.rollback();
477 }
478
479
480 /**
481 * Load name directory
482 */
483 private Map getNameDirectory()
484 throws IOException
485 {
486 // retrieve directory of named hashtable
487 long nameDirectory_recid = getRoot( NAME_DIRECTORY_ROOT );
488 if ( nameDirectory_recid == 0 ) {
489 _nameDirectory = new HashMap();
490 nameDirectory_recid = insert( _nameDirectory );
491 setRoot( NAME_DIRECTORY_ROOT, nameDirectory_recid );
492 } else {
493 _nameDirectory = (Map) fetch( nameDirectory_recid );
494 }
495 return _nameDirectory;
496 }
497
498
499 private void saveNameDirectory( Map directory )
500 throws IOException
501 {
502 long recid = getRoot( NAME_DIRECTORY_ROOT );
503 if ( recid == 0 ) {
504 throw new IOException( "Name directory must exist" );
505 }
506 update( recid, _nameDirectory );
507 }
508
509
510 /**
511 * Check if RecordManager has been closed. If so, throw an
512 * IllegalStateException.
513 */
514 private void checkIfClosed()
515 throws IllegalStateException
516 {
517 if ( _file == null ) {
518 throw new IllegalStateException( "RecordManager has been closed" );
519 }
520 }
521 }