public class HeapPage implements Page {
final HeapPageId pid;
final TupleDesc td;
final byte header[];
final Tuple tuples[];
final int numSlots;
byte[] oldData;
private final Byte oldDataLock = new Byte((byte) 0);
* Create a HeapPage from a set of bytes of data read from disk.
* The format of a HeapPage is a set of header bytes indicating
* the slots of the page that are in use, some number of tuple slots.
* Specifically, the number of tuples is equal to: <p>
* floor((BufferPool.getPageSize()*8) / (tuple size * 8 + 1))
* <p> where tuple size is the size of tuples in this
* database table, which can be determined via {@link Catalog#getTupleDesc}.
* The number of 8-bit header words is equal to:
* <p>
* ceiling(no. tuple slots / 8)
* <p>
*
* @see Database#getCatalog
* @see Catalog#getTupleDesc
* @see BufferPool#getPageSize()
*/
public HeapPage(HeapPageId id, byte[] data) throws IOException {
this.pid = id;
this.td = Database.getCatalog().getTupleDesc(id.getTableId());
this.numSlots = getNumTuples();
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
header = new byte[getHeaderSize()];
for (int i = 0; i < header.length; i++)
header[i] = dis.readByte();
tuples = new Tuple[numSlots];
try {
for (int i = 0; i < tuples.length; i++)
tuples[i] = readNextTuple(dis, i);
} catch (NoSuchElementException e) {
e.printStackTrace();
}
dis.close();
setBeforeImage();
}
* Retrieve the number of tuples on this page.
*
* @return the number of tuples on this page
*/
private int getNumTuples() {
return (BufferPool.getPageSize() * 8) / (td.getSize() * 8 + 1);
}
* Computes the number of bytes in the header of a page in a HeapFile with each tuple occupying tupleSize bytes
*
* @return the number of bytes in the header of a page in a HeapFile with each tuple occupying tupleSize bytes
*/
private int getHeaderSize() {
return (getNumTuples() + 7) / 8;
}
* Return a view of this page before it was modified
* -- used by recovery
*/
public HeapPage getBeforeImage() {
try {
byte[] oldDataRef = null;
synchronized (oldDataLock) {
oldDataRef = oldData;
}
return new HeapPage(pid, oldDataRef);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return null;
}
public void setBeforeImage() {
synchronized (oldDataLock) {
oldData = getPageData().clone();
}
}
* @return the PageId associated with this page.
*/
public HeapPageId getId() {
return this.pid;
}
* Suck up tuples from the source file.
*/
private Tuple readNextTuple(DataInputStream dis, int slotId) throws NoSuchElementException {
if (!isSlotUsed(slotId)) {
for (int i = 0; i < td.getSize(); i++) {
try {
dis.readByte();
} catch (IOException e) {
throw new NoSuchElementException("error reading empty tuple");
}
}
return null;
}
Tuple t = new Tuple(td);
RecordId rid = new RecordId(pid, slotId);
t.setRecordId(rid);
try {
for (int j = 0; j < td.numFields(); j++) {
Field f = td.getFieldType(j).parse(dis);
t.setField(j, f);
}
} catch (java.text.ParseException e) {
e.printStackTrace();
throw new NoSuchElementException("parsing error!");
}
return t;
}
* Generates a byte array representing the contents of this page.
* Used to serialize this page to disk.
* <p>
* The invariant here is that it should be possible to pass the byte
* array generated by getPageData to the HeapPage constructor and
* have it produce an identical HeapPage object.
*
* @return A byte array correspond to the bytes of this page.
* @see #HeapPage
*/
public byte[] getPageData() {
int len = BufferPool.getPageSize();
ByteArrayOutputStream baos = new ByteArrayOutputStream(len);
DataOutputStream dos = new DataOutputStream(baos);
for (int i = 0; i < header.length; i++) {
try {
dos.writeByte(header[i]);
} catch (IOException e) {
e.printStackTrace();
}
}
for (int i = 0; i < tuples.length; i++) {
if (!isSlotUsed(i)) {
for (int j = 0; j < td.getSize(); j++) {
try {
dos.writeByte(0);
} catch (IOException e) {
e.printStackTrace();
}
}
continue;
}
for (int j = 0; j < td.numFields(); j++) {
Field f = tuples[i].getField(j);
try {
f.serialize(dos);
} catch (IOException e) {
e.printStackTrace();
}
}
}
int zerolen = BufferPool.getPageSize() - (header.length + td.getSize() * tuples.length);
byte[] zeroes = new byte[zerolen];
try {
dos.write(zeroes, 0, zerolen);
} catch (IOException e) {
e.printStackTrace();
}
try {
dos.flush();
} catch (IOException e) {
e.printStackTrace();
}
return baos.toByteArray();
}
* Static method to generate a byte array corresponding to an empty
* HeapPage.
* Used to add new, empty pages to the file. Passing the results of
* this method to the HeapPage constructor will create a HeapPage with
* no valid tuples in it.
*
* @return The returned ByteArray.
*/
public static byte[] createEmptyPageData() {
int len = BufferPool.getPageSize();
return new byte[len];
}
* Delete the specified tuple from the page; the tuple should be updated to reflect
* that it is no longer stored on any page.
*
* @param t The tuple to delete
* @throws DbException if this tuple is not on this page, or tuple slot is
* already empty.
*/
public void deleteTuple(Tuple t) throws DbException {
assert t != null;
RecordId recToDelete = t.getRecordId();
if (recToDelete != null && pid.equals(recToDelete.pageId)) {
for (int i = 0; i < numSlots; i++) {
if (isSlotUsed(i) && t.getRecordId().equals(tuples[i].getRecordId())) {
markSlotUsed(i, false);
tuples[i] = null;
return;
}
}
throw new DbException("deleteTuple: Error: tuple slot is empty");
}
throw new DbException("deleteTuple: Error: tuple is not on this page");
}
* Adds the specified tuple to the page; the tuple should be updated to reflect
* that it is now stored on this page.
*
* @param t The tuple to add.
* @throws DbException if the page is full (no empty slots) or tupledesc
* is mismatch.
*/
public void insertTuple(Tuple t) throws DbException {
assert t != null;
if (td.equals(t.getTupleDesc())) {
for (int i = 0; i < numSlots; i++) {
if (!isSlotUsed(i)) {
markSlotUsed(i, true);
t.setRecordId(new RecordId(pid, i));
tuples[i] = t;
return;
}
}
throw new DbException("insertTuple: ERROR: no tuple is inserted");
}
throw new DbException("insertTuple: no empty slots or tupledesc is mismatch");
}
private TransactionId dirtier;
* Marks this page as dirty/not dirty and record that transaction
* that did the dirtying
*/
public void markDirty(boolean dirty, TransactionId tid) {
if (dirty) {
this.dirtier = tid;
} else {
this.dirtier = null;
}
}
* Returns the tid of the transaction that last dirtied this page, or null if the page is not dirty
*/
public TransactionId isDirty() {
return dirtier;
}
* Returns the number of empty slots on this page.
*/
public int getNumEmptySlots() {
int numEmptSlot = 0;
for (int i = 0; i < numSlots; i++) {
if (!isSlotUsed(i)) {
numEmptSlot += 1;
}
}
return numEmptSlot;
}
* Returns true if associated slot on this page is filled.
*/
public boolean isSlotUsed(int i) {
if (i < numSlots) {
int hdNo = i / 8;
int offset = i % 8;
return (header[hdNo] & (0x1 << offset)) != 0;
}
return false;
}
* Abstraction to fill or clear a slot on this page.
*/
private void markSlotUsed(int i, boolean value) {
if (i < numSlots) {
int hdNo = i / 8;
int offset = i % 8;
byte mask = (byte) (0x1 << offset);
if (value) {
header[hdNo] |= mask;
} else {
header[hdNo] &= ~mask;
}
}
}
protected class HeapPageTupleIterator implements Iterator<Tuple> {
private final Iterator<Tuple> iter;
public HeapPageTupleIterator() {
ArrayList<Tuple> tupleArrayList = new ArrayList<Tuple>(numSlots);
for (int i = 0; i < numSlots; i++) {
if (isSlotUsed(i)) {
tupleArrayList.add(i, tuples[i]);
}
}
iter = tupleArrayList.iterator();
}
@Override
public void remove() {
throw new UnsupportedOperationException("TupleIterator: remove not supported");
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public Tuple next() {
return iter.next();
}
}
* @return an iterator over all tuples on this page (calling remove on this iterator throws an UnsupportedOperationException)
* (note that this iterator shouldn't return tuples in empty slots!)
*/
public Iterator<Tuple> iterator() {
return new HeapPageTupleIterator();
}
}