int allocate(BitField exclude, int first) { if (this.full) { return -1; } int start = Math.max(0, first - getPos()); for (; ; ) { int free = this.used.nextClearBit(start); if (free >= this.pageCount) { if (start == 0) { this.full = true; } return -1; } if ((exclude != null) && (exclude.get(free + getPos()))) { start = exclude.nextClearBit(free + getPos()) - getPos(); if (start >= this.pageCount) { return -1; } } else { this.used.set(free); this.store.logUndo(this, this.data); this.store.update(this); return free + getPos(); } } }
/** * Allocate a page from the free list. * * @param exclude the exclude list or null * @param first the first page to look for * @return the page, or -1 if all pages are used */ int allocate(BitField exclude, int first) { if (full) { return -1; } // TODO cache last result int start = Math.max(0, first - getPos()); while (true) { int free = used.nextClearBit(start); if (free >= pageCount) { if (start == 0) { full = true; } return -1; } if (exclude != null && exclude.get(free + getPos())) { start = exclude.nextClearBit(free + getPos()) - getPos(); if (start >= pageCount) { return -1; } } else { // set the bit first, because logUndo can // allocate other pages, and we must not // return the same page twice used.set(free); store.logUndo(this, data); store.update(this); return free + getPos(); } } }
/** * Mark a page as used. * * @param pageId the page id * @return the page id, or -1 */ int allocate(int pageId) { int idx = pageId - getPos(); if (idx >= 0 && !used.get(idx)) { // set the bit first, because logUndo can // allocate other pages, and we must not // return the same page twice used.set(idx); store.logUndo(this, data); store.update(this); } return pageId; }
private PageFreeList(PageStore store, int pageId) { // kept in cache, and array list in page store setPos(pageId); this.store = store; pageCount = (store.getPageSize() - DATA_START) * 8; used = new BitField(pageCount); used.set(0); }
@Override public void write() { data = store.createData(); data.writeByte((byte) Page.TYPE_FREE_LIST); data.writeShortInt(0); for (int i = 0; i < pageCount; i += 8) { data.writeByte((byte) used.getByte(i)); } store.writePage(getPos(), data); }
/** Read the page from the disk. */ private void read() { data.reset(); data.readByte(); data.readShortInt(); for (int i = 0; i < pageCount; i += 8) { int x = data.readByte() & 255; used.setByte(i, x); } full = false; }
private void checkRegistered(int parameterIndex) throws SQLException { try { checkIndexBounds(parameterIndex); if (!outParameters.get(parameterIndex - 1)) { throw DbException.getInvalidValueException("parameterIndex", parameterIndex); } } catch (Exception e) { throw logAndConvert(e); } }
/** * Get the first free page starting at the given offset. * * @param first the page number to start the search * @return the page number, or -1 */ int getFirstFree(int first) { if (full) { return -1; } int start = Math.max(0, first - getPos()); int free = used.nextClearBit(start); if (free >= pageCount) { return -1; } return free + getPos(); }
/** * Add an undo entry to the log. The page data is only written once until the next checkpoint. * * @param pageId the page id * @param page the old page data */ void addUndo(int pageId, Data page) { if (undo.get(pageId) || freeing) { return; } if (trace.isDebugEnabled()) { trace.debug("log undo " + pageId); } if (SysProperties.CHECK) { if (page == null) { DbException.throwInternalError("Undo entry not written"); } } undo.set(pageId); undoAll.set(pageId); Data buffer = getBuffer(); buffer.writeByte((byte) UNDO); buffer.writeVarInt(pageId); if (page.getBytes()[0] == 0) { buffer.writeVarInt(1); } else { int pageSize = store.getPageSize(); if (COMPRESS_UNDO) { int size = compress.compress(page.getBytes(), pageSize, compressBuffer, 0); if (size < pageSize) { buffer.writeVarInt(size); buffer.checkCapacity(size); buffer.write(compressBuffer, 0, size); } else { buffer.writeVarInt(0); buffer.checkCapacity(pageSize); buffer.write(page.getBytes(), 0, pageSize); } } else { buffer.writeVarInt(0); buffer.checkCapacity(pageSize); buffer.write(page.getBytes(), 0, pageSize); } } write(buffer); }
private void registerOutParameter(int parameterIndex) throws SQLException { try { checkClosed(); if (outParameters == null) { maxOutParameters = Math.min( getParameterMetaData().getParameterCount(), getCheckedMetaData().getColumnCount()); outParameters = new BitField(); } checkIndexBounds(parameterIndex); ParameterInterface param = command.getParameters().get(--parameterIndex); if (!param.isValueSet()) { param.setValue(ValueNull.INSTANCE, false); } outParameters.set(parameterIndex); } catch (Exception e) { throw logAndConvert(e); } }
/** * Check if the undo entry was already written for the given page. * * @param pageId the page * @return true if it was written */ boolean getUndo(int pageId) { return undo.get(pageId); }
/** * Run one recovery stage. There are three recovery stages: 0: only the undo steps are run * (restoring the state before the last checkpoint). 1: the pages that are used by the transaction * log are allocated. 2: the committed operations are re-applied. * * @param stage the recovery stage * @return whether the transaction log was empty */ boolean recover(int stage) { if (trace.isDebugEnabled()) { trace.debug("log recover stage: " + stage); } if (stage == RECOVERY_STAGE_ALLOCATE) { PageInputStream in = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage); usedLogPages = in.allocateAllPages(); in.close(); return true; } PageInputStream pageIn = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage); DataReader in = new DataReader(pageIn); int logId = 0; Data data = store.createData(); boolean isEmpty = true; try { int pos = 0; while (true) { int x = in.readByte(); if (x < 0) { break; } pos++; isEmpty = false; if (x == UNDO) { int pageId = in.readVarInt(); int size = in.readVarInt(); if (size == 0) { in.readFully(data.getBytes(), store.getPageSize()); } else if (size == 1) { // empty Arrays.fill(data.getBytes(), 0, store.getPageSize(), (byte) 0); } else { in.readFully(compressBuffer, size); try { compress.expand(compressBuffer, 0, size, data.getBytes(), 0, store.getPageSize()); } catch (ArrayIndexOutOfBoundsException e) { DbException.convertToIOException(e); } } if (stage == RECOVERY_STAGE_UNDO) { if (!undo.get(pageId)) { if (trace.isDebugEnabled()) { trace.debug("log undo {0}", pageId); } store.writePage(pageId, data); undo.set(pageId); undoAll.set(pageId); } else { if (trace.isDebugEnabled()) { trace.debug("log undo skip {0}", pageId); } } } } else if (x == ADD) { int sessionId = in.readVarInt(); int tableId = in.readVarInt(); Row row = readRow(in, data); if (stage == RECOVERY_STAGE_UNDO) { store.allocateIfIndexRoot(pos, tableId, row); } else if (stage == RECOVERY_STAGE_REDO) { if (isSessionCommitted(sessionId, logId, pos)) { if (trace.isDebugEnabled()) { trace.debug("log redo + table: " + tableId + " s: " + sessionId + " " + row); } store.redo(tableId, row, true); } else { if (trace.isDebugEnabled()) { trace.debug("log ignore s: " + sessionId + " + table: " + tableId + " " + row); } } } } else if (x == REMOVE) { int sessionId = in.readVarInt(); int tableId = in.readVarInt(); long key = in.readVarLong(); if (stage == RECOVERY_STAGE_REDO) { if (isSessionCommitted(sessionId, logId, pos)) { if (trace.isDebugEnabled()) { trace.debug("log redo - table: " + tableId + " s:" + sessionId + " key: " + key); } store.redoDelete(tableId, key); } else { if (trace.isDebugEnabled()) { trace.debug("log ignore s: " + sessionId + " - table: " + tableId + " " + key); } } } } else if (x == TRUNCATE) { int sessionId = in.readVarInt(); int tableId = in.readVarInt(); if (stage == RECOVERY_STAGE_REDO) { if (isSessionCommitted(sessionId, logId, pos)) { if (trace.isDebugEnabled()) { trace.debug("log redo truncate table: " + tableId); } store.redoTruncate(tableId); } else { if (trace.isDebugEnabled()) { trace.debug("log ignore s: " + sessionId + " truncate table: " + tableId); } } } } else if (x == PREPARE_COMMIT) { int sessionId = in.readVarInt(); String transaction = in.readString(); if (trace.isDebugEnabled()) { trace.debug("log prepare commit " + sessionId + " " + transaction + " pos: " + pos); } if (stage == RECOVERY_STAGE_UNDO) { int page = pageIn.getDataPage(); setPrepareCommit(sessionId, page, transaction); } } else if (x == ROLLBACK) { int sessionId = in.readVarInt(); if (trace.isDebugEnabled()) { trace.debug("log rollback " + sessionId + " pos: " + pos); } // ignore - this entry is just informational } else if (x == COMMIT) { int sessionId = in.readVarInt(); if (trace.isDebugEnabled()) { trace.debug("log commit " + sessionId + " pos: " + pos); } if (stage == RECOVERY_STAGE_UNDO) { setLastCommitForSession(sessionId, logId, pos); } } else if (x == NOOP) { // nothing to do } else if (x == CHECKPOINT) { logId++; } else if (x == FREE_LOG) { int count = in.readVarInt(); for (int i = 0; i < count; i++) { int pageId = in.readVarInt(); if (stage == RECOVERY_STAGE_REDO) { if (!usedLogPages.get(pageId)) { store.free(pageId, false); } } } } else { if (trace.isDebugEnabled()) { trace.debug("log end"); break; } } } } catch (DbException e) { if (e.getErrorCode() == ErrorCode.FILE_CORRUPTED_1) { trace.debug("log recovery stopped"); } else { throw e; } } catch (IOException e) { trace.debug("log recovery completed"); } undo = new BitField(); if (stage == RECOVERY_STAGE_REDO) { usedLogPages = null; } return isEmpty; }
/** * Check if a page is already in use. * * @param pageId the page to check * @return true if it is in use */ boolean isUsed(int pageId) { return used.get(pageId - getPos()); }
/** * Add a page to the free list. * * @param pageId the page id to add */ void free(int pageId) { full = false; store.logUndo(this, data); used.clear(pageId - getPos()); store.update(this); }
int getLastUsed() { int last = used.length() - 1; return last <= 0 ? -1 : last + getPos(); }