1 5 package org.h2.store; 6 7 import java.io.IOException ; 8 import java.sql.SQLException ; 9 10 import org.h2.api.DatabaseEventListener; 11 import org.h2.engine.Constants; 12 import org.h2.engine.Database; 13 import org.h2.engine.Session; 14 import org.h2.message.Message; 15 import org.h2.message.Trace; 16 import org.h2.util.FileUtils; 17 import org.h2.util.MathUtils; 18 import org.h2.util.ObjectArray; 19 20 37 public class LogFile { 38 39 private static final int BUFFER_SIZE = 8 * 1024; 40 public static final int BLOCK_SIZE = 16; 41 42 private LogSystem logSystem; 43 private Database database; 44 private int id; 45 private String fileNamePrefix; 46 private String fileName; 47 private FileStore file; 48 private int bufferPos; 49 private byte[] buffer; 50 private ObjectArray unwritten; 51 private DataPage rowBuff; 52 private int pos = LogSystem.LOG_WRITTEN; 53 private int firstUncommittedPos = LogSystem.LOG_WRITTEN; 54 private int firstUnwrittenPos = LogSystem.LOG_WRITTEN; 55 56 LogFile(LogSystem log, int id, String fileNamePrefix) throws SQLException { 57 this.logSystem = log; 58 this.database = log.getDatabase(); 59 this.id = id; 60 this.fileNamePrefix = fileNamePrefix; 61 fileName = getFileName(id); 62 file = log.getDatabase().openFile(fileName, false); 63 rowBuff = log.getRowBuffer(); 64 buffer = new byte[BUFFER_SIZE]; 65 unwritten = new ObjectArray(); 66 try { 67 readHeader(); 68 writeHeader(); 69 pos = getBlock(); 70 firstUncommittedPos = pos; 71 } catch(SQLException e) { 72 close(false); 73 throw e; 74 } 75 } 76 77 static LogFile openIfLogFile(LogSystem log, String fileNamePrefix, String fileName) throws SQLException { 78 if(!fileName.endsWith(Constants.SUFFIX_LOG_FILE)) { 79 return null; 80 } 81 if(!FileUtils.fileStartsWith(fileName, fileNamePrefix+".")) { 82 return null; 83 } 84 String s = fileName.substring(fileNamePrefix.length()+1, fileName.length()-Constants.SUFFIX_LOG_FILE.length()); 85 int id = Integer.parseInt(s); 86 return new LogFile(log, id, fileNamePrefix); 87 } 88 89 private String getFileName(int id) { 90 return fileNamePrefix + "." + id + Constants.SUFFIX_LOG_FILE; 91 } 92 93 public int getId() { 94 return id; 95 } 96 97 private int getBlock() throws SQLException { 98 if(file == null) { 99 throw Message.getSQLException(Message.SIMULATED_POWER_OFF); 100 } 101 return (int)(file.getFilePointer() / BLOCK_SIZE); 102 } 103 104 private void writeBuffer(DataPage buff, Record rec) throws SQLException { 105 if(file == null) { 106 throw Message.getSQLException(Message.SIMULATED_POWER_OFF); 107 } 108 int size = MathUtils.roundUp(buff.length()+buff.getFillerLength(), BLOCK_SIZE); 109 int blockCount = size / BLOCK_SIZE; 110 buff.fill(size); 111 buff.setInt(0, blockCount); 112 buff.updateChecksum(); 113 if (buff.length() + bufferPos > buffer.length) { 115 flush(); 117 } 118 if (buff.length() >= buffer.length) { 119 file.write(buff.getBytes(), 0, buff.length()); 121 pos = getBlock(); 122 return; 123 } 124 System.arraycopy(buff.getBytes(), 0, buffer, bufferPos, buff.length()); 125 bufferPos += buff.length(); 126 if(rec != null) { 127 unwritten.add(rec); 128 } 129 pos = getBlock() + (bufferPos / BLOCK_SIZE); 130 } 131 132 void commit(Session session) throws SQLException { 133 DataPage buff = rowBuff; 134 buff.reset(); 135 buff.writeInt(0); 136 buff.writeByte((byte)'C'); 137 buff.writeInt(session.getId()); 138 writeBuffer(buff, null); 139 if(logSystem.getFlushOnEachCommit()) { 140 flush(); 141 } 142 } 143 144 void prepareCommit(Session session, String transaction) throws SQLException { 145 DataPage buff = rowBuff; 146 buff.reset(); 147 buff.writeInt(0); 148 buff.writeByte((byte)'P'); 149 buff.writeInt(session.getId()); 150 buff.writeString(transaction); 151 writeBuffer(buff, null); 152 if(logSystem.getFlushOnEachCommit()) { 153 flush(); 154 } 155 } 156 157 private DataPage readPage() throws SQLException { 158 byte[] buff = new byte[BLOCK_SIZE]; 159 file.readFully(buff, 0, BLOCK_SIZE); 160 DataPage s = DataPage.create(database, buff); 161 int blocks = Math.abs(s.readInt()); 162 if(blocks > 1) { 163 byte[] b2 = new byte[blocks * BLOCK_SIZE]; 164 System.arraycopy(buff, 0, b2, 0, BLOCK_SIZE); 165 buff = b2; 166 file.readFully(buff, BLOCK_SIZE, blocks * BLOCK_SIZE - BLOCK_SIZE); 167 s = DataPage.create(database, buff); 168 s.check(blocks * BLOCK_SIZE); 169 } else { 170 s.reset(); 171 } 172 return s; 173 } 174 175 private boolean redoOrUndo(boolean undo) throws SQLException { 176 int pos = getBlock(); 177 DataPage in = readPage(); 178 int blocks = in.readInt(); 179 if(blocks < 0) { 180 return true; 181 } else if(blocks == 0) { 182 go(pos); 183 file.setLength((long)pos * BLOCK_SIZE); 184 return false; 185 } 186 char type = (char)in.readByte(); 187 int sessionId = in.readInt(); 188 if(type == 'P') { 189 if(undo) { 190 throw Message.getInternalError("can't undo prepare commit"); 191 } 192 String transaction = in.readString(); 193 logSystem.setPreparedCommitForSession(this, sessionId, pos, transaction, blocks); 194 return true; 195 } else if(type == 'C') { 196 if(undo) { 197 throw Message.getInternalError("can't undo commit"); 198 } 199 logSystem.setLastCommitForSession(sessionId, id, pos); 200 return true; 201 } else if(type == 'R') { 202 if(undo) { 203 throw Message.getInternalError("can't undo rollback"); 204 } 205 return true; 206 } else if(type == 'S') { 207 if(undo) { 208 throw Message.getInternalError("can't undo summary"); 209 } 210 } 211 if(undo) { 212 if(logSystem.isSessionCommitted(sessionId, id, pos)) { 213 logSystem.removeSession(sessionId); 214 return true; 215 } 216 } else { 217 if(type != 'S') { 218 logSystem.addUndoLogRecord(this, pos, sessionId); 219 } 220 } 221 int storageId = in.readInt(); 222 Storage storage = logSystem.getStorageForRecovery(storageId); 223 DataPage rec = null; 224 int recordId = in.readInt(); 225 int blockCount = in.readInt(); 226 if(type != 'T') { 227 rec = in.readDataPageNoSize(); 228 } 229 switch(type) { 230 case 'S': { 231 int fileType = in.readByte(); 232 boolean diskFile; 233 if(fileType == 'D') { 234 diskFile = true; 235 } else if(fileType == 'I') { 236 diskFile = false; 237 } else { 238 break; 240 } 241 int sumLength= in.readInt(); 242 byte[] summary = new byte[sumLength]; 243 if(sumLength > 0) { 244 in.read(summary, 0, sumLength); 245 } 246 if(diskFile) { 247 database.getDataFile().initFromSummary(summary); 248 } else { 249 database.getIndexFile().initFromSummary(summary); 250 } 251 break; 252 } 253 case 'T': 254 if(undo) { 255 throw Message.getInternalError("cannot undo truncate"); 256 } else { 257 logSystem.addRedoLog(storage, recordId, blockCount, null); 258 storage.setRecordCount(0); 259 storage.getDiskFile().setPageOwner(recordId / DiskFile.BLOCKS_PER_PAGE, -1); 260 logSystem.setLastCommitForSession(sessionId, id, pos); 261 } 262 break; 263 case 'I': 264 if(undo) { 265 logSystem.addRedoLog(storage, recordId, blockCount, null); 266 storage.setRecordCount(storage.getRecordCount()-1); 267 } else { 268 logSystem.addRedoLog(storage, recordId, blockCount, rec); 269 storage.setRecordCount(storage.getRecordCount()+1); 270 } 271 break; 272 case 'D': 273 if(undo) { 274 logSystem.addRedoLog(storage, recordId, blockCount, rec); 275 storage.setRecordCount(storage.getRecordCount()+1); 276 } else { 277 logSystem.addRedoLog(storage, recordId, blockCount, null); 278 storage.setRecordCount(storage.getRecordCount()-1); 279 } 280 break; 281 default: 282 throw Message.getInternalError("type="+type); 283 } 284 return true; 285 } 286 287 public void redoAllGoEnd() throws SQLException { 288 long length = file.length(); 289 if(length<=FileStore.HEADER_LENGTH) { 290 return; 291 } 292 try { 293 int max = (int)(length / BLOCK_SIZE); 294 while(true) { 295 pos = getBlock(); 296 database.setProgress(DatabaseEventListener.STATE_RECOVER, fileName, pos, max); 297 if((long)pos * BLOCK_SIZE >= length) { 298 break; 299 } 300 boolean more = redoOrUndo(false); 301 if(!more) { 302 break; 303 } 304 } 305 database.setProgress(DatabaseEventListener.STATE_RECOVER, fileName, max, max); 306 } catch(SQLException e) { 307 database.getTrace(Trace.LOG).debug("Stop reading log file: "+e.getMessage(), e); 308 } catch(OutOfMemoryError e) { 310 throw Message.convert(e); 313 } catch(Throwable e) { 314 database.getTrace(Trace.LOG).error("Error reading log file (non-fatal)", e); 315 } 319 go(pos); 320 } 321 322 void go(int pos) throws SQLException { 323 file.seek((long)pos * BLOCK_SIZE); 324 } 325 326 void undo(int pos) throws SQLException { 327 go(pos); 328 redoOrUndo(true); 329 } 330 331 void flush() throws SQLException { 332 if(bufferPos > 0) { 333 if(file == null) { 334 throw Message.getSQLException(Message.SIMULATED_POWER_OFF); 335 } 336 file.write(buffer, 0, bufferPos); 337 for(int i=0; i<unwritten.size(); i++) { 338 Record r = (Record) unwritten.get(i); 339 r.setLogWritten(id, pos); 340 } 341 unwritten.clear(); 342 bufferPos = 0; 343 pos = getBlock(); 344 long min = (long)pos * BLOCK_SIZE; 345 min = MathUtils.scaleUp50Percent(128 * 1024, min, 8 * 1024); 346 if(min > file.length()) { 347 file.setLength(min); 348 } 349 } 350 } 351 352 void close(boolean delete) throws SQLException { 353 SQLException closeException = null; 354 try { 355 flush(); 356 } catch (SQLException e) { 357 closeException = e; 358 } 359 if(file != null) { 361 try { 362 file.close(); 363 file = null; 364 if(delete) { 365 FileUtils.delete(fileName); 366 } 367 } catch (IOException e) { 368 if(closeException == null) { 369 closeException = Message.convert(e); 370 } 371 } 372 file = null; 373 fileNamePrefix = null; 374 } 375 if(closeException != null) { 376 throw closeException; 377 } 378 } 379 380 void addSummary(boolean dataFile, byte[] summary) throws SQLException { 381 DataPage buff = DataPage.create(database, 256); 382 buff.writeInt(0); 383 buff.writeByte((byte)'S'); 384 buff.writeInt(0); 385 buff.writeInt(0); buff.writeInt(0); buff.writeInt(0); buff.writeByte((byte)(dataFile ? 'D' : 'I')); 389 if(summary == null) { 390 buff.writeInt(0); 391 } else { 392 buff.checkCapacity(summary.length); 393 buff.writeInt(summary.length); 394 buff.write(summary, 0, summary.length); 395 } 396 writeBuffer(buff, null); 397 } 398 399 void addTruncate(Session session, int storageId, int recordId, int blockCount) throws SQLException { 400 DataPage buff = rowBuff; 401 buff.reset(); 402 buff.writeInt(0); 403 buff.writeByte((byte)'T'); 404 buff.writeInt(session.getId()); 405 buff.writeInt(storageId); 406 buff.writeInt(recordId); 407 buff.writeInt(blockCount); 408 writeBuffer(buff, null); 409 } 410 411 void add(Session session, int storageId, Record record) throws SQLException { 412 record.prepareWrite(); 413 DataPage buff = rowBuff; 414 buff.reset(); 415 buff.writeInt(0); 416 if(record.getDeleted()) { 417 buff.writeByte((byte)'D'); 418 } else { 419 buff.writeByte((byte)'I'); 420 } 421 buff.writeInt(session.getId()); 422 buff.writeInt(storageId); 423 buff.writeInt(record.getPos()); 424 int blockCount = record.getBlockCount(); 425 buff.writeInt(blockCount); 426 buff.checkCapacity(DiskFile.BLOCK_SIZE * blockCount); 427 record.write(buff); 428 writeBuffer(buff, record); 429 } 430 431 void setFirstUncommittedPos(int firstUncommittedPos) throws SQLException { 432 this.firstUncommittedPos = firstUncommittedPos; 433 int pos = getBlock(); 434 writeHeader(); 435 go(pos); 436 } 437 438 int getFirstUncommittedPos() { 439 return firstUncommittedPos; 440 } 441 442 private void writeHeader() throws SQLException { 443 file.seek(FileStore.HEADER_LENGTH); 444 DataPage buff = getHeader(); 445 file.write(buff.getBytes(), 0, buff.length()); 446 } 447 448 private DataPage getHeader() { 449 DataPage buff = rowBuff; 450 buff.reset(); 451 buff.writeInt(id); 452 buff.writeInt(firstUncommittedPos); 453 buff.writeInt(firstUnwrittenPos); 455 buff.fill(3*BLOCK_SIZE); 456 return buff; 457 } 458 459 private void readHeader() throws SQLException { 460 DataPage buff = getHeader(); 461 int len = buff.length(); 462 buff.reset(); 463 if(file.length() < FileStore.HEADER_LENGTH + len) { 464 return; 466 } 467 file.readFully(buff.getBytes(), 0, len); 468 id = buff.readInt(); 469 firstUncommittedPos = buff.readInt(); 470 firstUnwrittenPos = buff.readInt(); 471 } 472 473 int getPos() { 474 return pos; 475 } 476 477 public long getFileSize() throws SQLException { 478 return file.getFilePointer(); 479 } 480 481 public void sync() { 482 if(file != null) { 483 file.sync(); 484 } 485 } 486 487 public void updatePreparedCommit(boolean commit, int pos, int sessionId, int blocks) throws SQLException { 488 synchronized(database) { 489 int posNow = getBlock(); 490 DataPage buff = rowBuff; 491 buff.reset(); 492 buff.writeInt(blocks); 493 if(commit) { 494 buff.writeByte((byte)'C'); 495 } else { 496 buff.writeByte((byte)'R'); 497 } 498 buff.writeInt(sessionId); 499 buff.fill(blocks * BLOCK_SIZE); 500 buff.updateChecksum(); 501 go(pos); 502 file.write(buff.getBytes(), 0, BLOCK_SIZE * blocks); 503 go(posNow); 504 } 505 } 506 507 } 508 | Popular Tags |