/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.engine;

import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.store.Data;
import org.h2.store.FileStore;
import org.h2.table.Table;
import org.h2.value.Value;

An entry in a undo log.
/** * An entry in a undo log. */
public class UndoLogRecord {
Operation type meaning the row was inserted.
/** * Operation type meaning the row was inserted. */
public static final short INSERT = 0;
Operation type meaning the row was deleted.
/** * Operation type meaning the row was deleted. */
public static final short DELETE = 1; private static final int IN_MEMORY = 0, STORED = 1, IN_MEMORY_INVALID = 2; private Table table; private Row row; private short operation; private short state; private int filePos;
Create a new undo log record
Params:
  • table – the table
  • op – the operation type
  • row – the row that was deleted or inserted
/** * Create a new undo log record * * @param table the table * @param op the operation type * @param row the row that was deleted or inserted */
UndoLogRecord(Table table, short op, Row row) { this.table = table; this.row = row; this.operation = op; this.state = IN_MEMORY; }
Check if the log record is stored in the file.
Returns:true if it is
/** * Check if the log record is stored in the file. * * @return true if it is */
boolean isStored() { return state == STORED; }
Check if this undo log record can be store. Only record can be stored if the table has a unique index.
Returns:if it can be stored
/** * Check if this undo log record can be store. Only record can be stored if * the table has a unique index. * * @return if it can be stored */
boolean canStore() { // if large transactions are enabled, this method is not called return table.getUniqueIndex() != null; }
Un-do the operation. If the row was inserted before, it is deleted now, and vice versa.
Params:
  • session – the session
/** * Un-do the operation. If the row was inserted before, it is deleted now, * and vice versa. * * @param session the session */
void undo(Session session) { Database db = session.getDatabase(); switch (operation) { case INSERT: if (state == IN_MEMORY_INVALID) { state = IN_MEMORY; } if (db.getLockMode() == Constants.LOCK_MODE_OFF) { if (row.isDeleted()) { // it might have been deleted by another thread return; } } try { row.setDeleted(false); table.removeRow(session, row); table.fireAfterRow(session, row, null, true); } catch (DbException e) { if (session.getDatabase().getLockMode() == Constants.LOCK_MODE_OFF && e.getErrorCode() == ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1) { // it might have been deleted by another thread // ignore } else { throw e; } } break; case DELETE: try { table.addRow(session, row); table.fireAfterRow(session, null, row, true); } catch (DbException e) { if (session.getDatabase().getLockMode() == Constants.LOCK_MODE_OFF && e.getSQLException().getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { // it might have been added by another thread // ignore } else { throw e; } } break; default: DbException.throwInternalError("op=" + operation); } }
Append the row to the buffer.
Params:
  • buff – the buffer
  • log – the undo log
/** * Append the row to the buffer. * * @param buff the buffer * @param log the undo log */
void append(Data buff, UndoLog log) { int p = buff.length(); buff.writeInt(0); buff.writeInt(operation); buff.writeByte(row.isDeleted() ? (byte) 1 : (byte) 0); buff.writeInt(log.getTableId(table)); buff.writeLong(row.getKey()); int count = row.getColumnCount(); buff.writeInt(count); for (int i = 0; i < count; i++) { Value v = row.getValue(i); buff.checkCapacity(buff.getValueLen(v)); buff.writeValue(v); } buff.fillAligned(); buff.setInt(p, (buff.length() - p) / Constants.FILE_BLOCK_SIZE); }
Save the row in the file using a buffer.
Params:
  • buff – the buffer
  • file – the file
  • log – the undo log
/** * Save the row in the file using a buffer. * * @param buff the buffer * @param file the file * @param log the undo log */
void save(Data buff, FileStore file, UndoLog log) { buff.reset(); append(buff, log); filePos = (int) (file.getFilePointer() / Constants.FILE_BLOCK_SIZE); file.write(buff.getBytes(), 0, buff.length()); row = null; state = STORED; }
Load an undo log record row using a buffer.
Params:
  • buff – the buffer
  • log – the log
Returns:the undo log record
/** * Load an undo log record row using a buffer. * * @param buff the buffer * @param log the log * @return the undo log record */
static UndoLogRecord loadFromBuffer(Data buff, UndoLog log) { UndoLogRecord rec = new UndoLogRecord(null, (short) 0, null); int pos = buff.length(); int len = buff.readInt() * Constants.FILE_BLOCK_SIZE; rec.load(buff, log); buff.setPos(pos + len); return rec; }
Load an undo log record row using a buffer.
Params:
  • buff – the buffer
  • file – the source file
  • log – the log
/** * Load an undo log record row using a buffer. * * @param buff the buffer * @param file the source file * @param log the log */
void load(Data buff, FileStore file, UndoLog log) { int min = Constants.FILE_BLOCK_SIZE; log.seek(filePos); buff.reset(); file.readFully(buff.getBytes(), 0, min); int len = buff.readInt() * Constants.FILE_BLOCK_SIZE; buff.checkCapacity(len); if (len - min > 0) { file.readFully(buff.getBytes(), min, len - min); } int oldOp = operation; load(buff, log); if (operation != oldOp) { DbException.throwInternalError("operation=" + operation + " op=" + oldOp); } } private void load(Data buff, UndoLog log) { operation = (short) buff.readInt(); boolean deleted = buff.readByte() == 1; table = log.getTable(buff.readInt()); long key = buff.readLong(); int columnCount = buff.readInt(); Value[] values = new Value[columnCount]; for (int i = 0; i < columnCount; i++) { values[i] = buff.readValue(); } row = getTable().getDatabase().createRow(values, Row.MEMORY_CALCULATE); row.setKey(key); row.setDeleted(deleted); state = IN_MEMORY_INVALID; }
Get the table.
Returns:the table
/** * Get the table. * * @return the table */
public Table getTable() { return table; }
Get the position in the file.
Returns:the file position
/** * Get the position in the file. * * @return the file position */
public long getFilePos() { return filePos; }
Get the row that was deleted or inserted.
Returns:the row
/** * Get the row that was deleted or inserted. * * @return the row */
public Row getRow() { return row; }
Change the state from IN_MEMORY to IN_MEMORY_INVALID. This method is called if a later record was read from the temporary file, and therefore the position could have changed.
/** * Change the state from IN_MEMORY to IN_MEMORY_INVALID. This method is * called if a later record was read from the temporary file, and therefore * the position could have changed. */
void invalidatePos() { if (this.state == IN_MEMORY) { state = IN_MEMORY_INVALID; } } }