Quadcap Embeddable Database

com/quadcap/sql/file/SubPageManager.java

Go to the documentation of this file.
00001 package com.quadcap.sql.file; 00002 00003 /* Copyright 1997 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.IOException; 00042 import java.io.InputStream; 00043 import java.io.OutputStream; 00044 00045 import com.quadcap.util.ConfigNumber; 00046 import com.quadcap.util.Debug; 00047 import com.quadcap.util.Util; 00048 00049 /** 00050 * This class implements a sub-page manager which operates by allocating 00051 * pages as needed from the main file and chopping them up into smaller 00052 * pages of the appropriate size. 00053 * 00054 * @author Stan Bailes 00055 */ 00056 public class SubPageManager implements PageManager { 00057 BlockFile file; 00058 /** 00059 * rootBlock is the root block of the entire block file, and will 00060 * always be zero in this implementation. 00061 */ 00062 long rootBlock; 00063 00064 /** The size of a subpage */ 00065 int pageSize; 00066 00067 /** The page shift (file.pageSize >> pageShift == pageSize) */ 00068 int pageShift; 00069 00070 /** The offset into the root block of our freelist head */ 00071 int offset; 00072 00073 /** (1 << pageShift) */ 00074 int pagesPerBlock; 00075 00076 // kinda unique. 00077 static final long PAGE_FREE = 0xabcddbca5438182fL; 00078 00079 /** 00080 * Construct a new sub-page manager bound to the specified block file 00081 */ 00082 public SubPageManager(BlockFile file, int pageShift, long rootBlock) 00083 throws IOException 00084 { 00085 this.file = file; 00086 this.pageShift = pageShift; 00087 this.rootBlock = rootBlock; 00088 this.offset = BlockFile.oSUBPAGE_ROOT + pageShift * BlockFile.REF_SIZE; 00089 this.pageSize = file.getPageSize() >> pageShift; 00090 this.pagesPerBlock = 1 << pageShift; 00091 if (pageSize < BlockFile.REF_SIZE * 2) { 00092 throw new IOException("Sub page too small: " + pageSize); 00093 } 00094 } 00095 00096 /** 00097 * Allocate a new block. 00098 */ 00099 public long newPage() throws IOException { 00100 long ret = -1; 00101 synchronized (file.fileLock) { 00102 Block root = file.getBlock(rootBlock); 00103 Page page = null; 00104 try { 00105 ret = root.readLong(offset); 00106 if (ret == 0) { 00107 ret = allocateNewBlock(); 00108 } 00109 page = getPage(ret); 00110 long next = page.readLong(0); 00111 if (page.readLong(BlockFile.REF_SIZE) != PAGE_FREE) { 00112 //#ifdef DEBUG 00113 byte[] ff = new byte[pageSize]; 00114 page.read(0, ff, 0, pageSize); 00115 Debug.println("Page: " + Util.strBytes(ff)); 00116 Debug.println("" + toString() + 00117 ".newPage() " + 00118 toString(ret) + 00119 ", freelist corrupted: "); 00120 //#endif 00121 throw new DatafileException("" + toString() + 00122 ".newPage() " + 00123 toString(ret) + 00124 ", freelist corrupted: "); 00125 00126 } 00127 root.writeLong(offset, next); 00128 page.clear(); // Can we let caller do this? 00129 } finally { 00130 if (page != null) page.decrRefCount(); 00131 root.decrRefCount(); 00132 } 00133 //#ifdef DEBUG 00134 if (Trace.bit(11)) { 00135 Debug.println("" + toString() + ".newPage() = " + 00136 toString(ret)); 00137 } 00138 if (pageShift(ret) != pageShift) { 00139 throw new DatafileException("" + toString() + ".newPage(), " + 00140 toString(ret) + 00141 ", freelist corrupted"); 00142 } 00143 //#endif 00144 } 00145 return ret; 00146 } 00147 00148 /** 00149 * Free a block. 00150 * 00151 * @param block a previously allocated block 00152 */ 00153 public void freePage(long block) throws IOException { 00154 //- //#ifdef PARANOID 00155 //- checkPage(block); 00156 //#endif 00157 //#ifdef DEBUG 00158 if (Trace.bit(11)) { 00159 Debug.println("" + toString() + ".freePage(" + 00160 toString(block) + ")"); 00161 } 00162 //#endif 00163 if (pageShift(block) != pageShift) { 00164 throw new DatafileException("" + toString() + ".freePage(" + 00165 toString(block) + "), bad page"); 00166 } 00167 synchronized (file.fileLock) { 00168 Block root = file.getBlock(rootBlock); 00169 try { 00170 long head = root.readLong(offset); 00171 if (head == block) { 00172 throw new DatafileException("" + toString() + ",freePage(" + 00173 toString(block) + "), already" + 00174 " free"); 00175 } 00176 Page page = getPage(block); 00177 page.writeLong(0, head); 00178 if (page.readLong(BlockFile.REF_SIZE) == PAGE_FREE) { 00179 throw new DatafileException("" + toString() + ",freePage(" + 00180 toString(block) + "), already" + 00181 " free"); 00182 } 00183 page.writeLong(BlockFile.REF_SIZE, PAGE_FREE); 00184 root.writeLong(offset, block); 00185 page.decrRefCount(); 00186 } finally { 00187 root.decrRefCount(); 00188 } 00189 } 00190 } 00191 00192 /** 00193 * Allocate new block and slice it up into sub-pages. Link the sub-pages 00194 * together and return the head of the list. Lock must be held! 00195 */ 00196 private final long allocateNewBlock() throws IOException { 00197 long blk = file.newPage(); 00198 //#ifdef DEBUG 00199 if (Trace.bit(11)) { 00200 Debug.println("" + toString() + ".allocateNewBlock: " + blk); 00201 } 00202 //#endif 00203 Block b = file.getBlock(blk); 00204 b.writeLong(0, 0); 00205 b.writeLong(BlockFile.REF_SIZE, PAGE_FREE); 00206 try { 00207 long p = makePageNo(blk, 0); 00208 for (int i = 1; i < pagesPerBlock; i++) { 00209 long np = makePageNo(blk, i); 00210 Page page = getPage(np); 00211 try { 00212 page.writeLong(0, p); 00213 page.writeLong(BlockFile.REF_SIZE, PAGE_FREE); 00214 p = np; 00215 } finally { 00216 page.decrRefCount(); 00217 } 00218 } 00219 return p; 00220 } finally { 00221 b.decrRefCount(); 00222 } 00223 } 00224 00225 /** 00226 * Sub-page numbers are encoded as follows:<p> 00227 * <pre> 00228 * sub_page { 00229 * pageShift bit4 00230 * pageNum bit16 00231 * blockNum bit44 00232 * } 00233 * </pre><p> 00234 * 00235 * This has the desirable property of being backward compatible with the 00236 * pre-sub-page block numbers, which will have pageShift and pageNum 00237 * equal to zero. 00238 */ 00239 00240 static int[] PAGE_SHIFT = {63,4}; 00241 static int[] PAGE_NUM = {59,16}; 00242 //- //#ifdef PARANOID 00243 //- static int[] MAGIC = {43,4}; 00244 //- static int[] BLOCK_NUM = {39,40}; 00245 //#else 00246 static int[] BLOCK_NUM = {43,44}; 00247 //#endif 00248 00249 static final long fIns(long v, int[] f) { 00250 return (v & fMask(f)) << fEnd(f); 00251 } 00252 static final long fMask(int[] f) { 00253 return (1L << f[1]) - 1; 00254 } 00255 static final long fEnd(int[] f) { 00256 return f[0] - f[1] + 1; 00257 } 00258 static final long fExt(long v, int[] f) { 00259 return (v >> fEnd(f)) & fMask(f); 00260 } 00261 00262 final long makePageNo(long blk, long page) { 00263 // we could mask here to "prevent" errors, but that really doesnt' 00264 // help. We could range check 'blk' and 'page' but we want this 00265 // fn to be fast, and hope we can be assured that the rest of the 00266 // code ensures the invariants. 00267 long ret = fIns(pageShift, PAGE_SHIFT) 00268 //- //#ifdef PARANOID 00269 //- | fIns(0xc, MAGIC) 00270 //#endif 00271 | fIns(page, PAGE_NUM) 00272 | fIns(blk, BLOCK_NUM); 00273 //- //#ifdef PARANOID 00274 //- if (fExt(ret, BLOCK_NUM) != blk) { 00275 //- Debug.println("pageShift: " + pageShift + ": " + 00276 //- fIns(pageShift, PAGE_SHIFT)); 00277 //- Debug.println("page: " + page + ": " + 00278 //- fIns(page, PAGE_NUM)); 00279 //- Debug.println("block: " + blk + ": " + 00280 //- fIns(blk, BLOCK_NUM)); 00281 //- Debug.println("============Bad blk: " + blk + ", " + page + 00282 //- ", " + toString(ret)); 00283 //- } 00284 //#endif 00285 return ret; 00286 } 00287 00288 /** 00289 * Return the number of the block that contains this page. 00290 */ 00291 public static final long pageBlock(long page) { 00292 //- //#ifdef PARANOID 00293 //- checkPage(page); 00294 //#endif 00295 return fExt(page, BLOCK_NUM); 00296 } 00297 00298 //- //#ifdef PARANOID 00299 //- static public void checkPage(long page) { 00300 //- if (fExt(page, PAGE_SHIFT) != 0 && 00301 //- (fExt(page, MAGIC) != 0xc || 00302 //- (fExt(page, PAGE_NUM) >= (1 << fExt(page, PAGE_SHIFT))))) { 00303 //- String s = "Page[ps:" + fExt(page, PAGE_SHIFT) + 00304 //- " blk:" + fExt(page, BLOCK_NUM) + 00305 //- " off:" + fExt(page, PAGE_NUM) + ']'; 00306 //- throw new RuntimeException("Bad page: " + s); 00307 //- } 00308 //- } 00309 //#endif 00310 00311 /** 00312 * Return the byte offset of this page in the base block. 00313 */ 00314 public final int pageOffset(long page) { 00315 //- //#ifdef PARANOID 00316 //- checkPage(page); 00317 //#endif 00318 return (int)(fExt(page, PAGE_NUM) * pageSize); 00319 } 00320 00321 /** 00322 * Return the page shift for this page. 00323 */ 00324 static final int pageShift(long page) { 00325 //- //#ifdef PARANOID 00326 //- checkPage(page); 00327 //#endif 00328 return (int)(fExt(page, PAGE_SHIFT)); 00329 } 00330 00331 /** 00332 * Return the specified page 00333 */ 00334 public Page getPage(long block) throws IOException { 00335 //- //#ifdef PARANOID 00336 //- checkPage(block); 00337 //#endif 00338 return new SubPage(this, block); 00339 } 00340 00341 /** 00342 * Return the (file) lock 00343 */ 00344 public Object getLock() { return file.getLock(); } 00345 00346 /** 00347 * Return this manager's block size 00348 */ 00349 public final int getPageSize() { 00350 return pageSize; 00351 } 00352 00353 /** 00354 * Return a new input stream, reading from the region with the specified 00355 * root block. 00356 * 00357 * @param block the root block of the region 00358 * @return an InputStream bound to the region. 00359 * @exception IOException if the block number isn't valid, or if another 00360 * error is detected trying to access the region. 00361 */ 00362 public RandomAccessInputStream getInputStream(long block) 00363 throws IOException 00364 { 00365 return new RandomAccessInputStream(getStream(block)); 00366 } 00367 00368 /** 00369 * Return a new output stream, writing to the region with the specified 00370 * root block. 00371 * 00372 * @param block the root block of the region 00373 * @return an OutputStream bound to the region. 00374 * @exception IOException if the block number isn't valid, or if another 00375 * error is detected trying to access the region. 00376 */ 00377 public RandomAccessOutputStream getOutputStream(long block) 00378 throws IOException 00379 { 00380 return new RandomAccessOutputStream(getStream(block)); 00381 } 00382 00383 public RandomAccess getStream(long blockRef) throws IOException { 00384 return new BlockAccess(this, blockRef); 00385 } 00386 00387 /** 00388 * Destroy the stream with the specified root page and free up the 00389 * storage it was using. 00390 * 00391 * @param page the root page of the region 00392 * 00393 * @exception IOException if the page number isn't valid, or if another 00394 * error is detected trying to access the region. 00395 */ 00396 public void freeStream(long page) throws IOException { 00397 synchronized (file.fileLock) { 00398 getStream(page).resize(0); 00399 freePage(page); 00400 } 00401 } 00402 00403 //#ifdef DEBUG 00404 /** 00405 * Return a displayable representation for this object. 00406 */ 00407 public String toString() { 00408 return "PM" + pageSize + ":[" + rootBlock + "," + offset + "]"; 00409 } 00410 //#endif 00411 00412 public static String toString(long page) { 00413 int psize = 8192 >> pageShift(page); 00414 long pnum = fExt(page, PAGE_NUM); 00415 return "Page(" + psize + 00416 ")[" + pageBlock(page) + 00417 ":" + pnum + " (" + (pnum * psize) + ")" + 00418 ']'; 00419 } 00420 }