Untitled
unknown
plain_text
3 years ago
14 kB
10
Indexable
package edu.berkeley.cs186.database.index;
import edu.berkeley.cs186.database.common.Buffer;
import edu.berkeley.cs186.database.common.Pair;
import edu.berkeley.cs186.database.concurrency.LockContext;
import edu.berkeley.cs186.database.databox.DataBox;
import edu.berkeley.cs186.database.databox.Type;
import edu.berkeley.cs186.database.memory.BufferManager;
import edu.berkeley.cs186.database.memory.Page;
import edu.berkeley.cs186.database.table.RecordId;
import java.nio.ByteBuffer;
import java.util.*;
/**
* A inner node of a B+ tree. Every inner node in a B+ tree of order d stores
* between d and 2d keys. An inner node with n keys stores n + 1 "pointers" to
* children nodes (where a pointer is just a page number). Moreover, every
* inner node is serialized and persisted on a single page; see toBytes and
* fromBytes for details on how an inner node is serialized. For example, here
* is an illustration of an order 2 inner node:
*
* +----+----+----+----+
* | 10 | 20 | 30 | |
* +----+----+----+----+
* / | | \
*/
class InnerNode extends BPlusNode {
// Metadata about the B+ tree that this node belongs to.
private BPlusTreeMetadata metadata;
// Buffer manager
private BufferManager bufferManager;
// Lock context of the B+ tree
private LockContext treeContext;
// The page on which this leaf is serialized.
private Page page;
// The keys and child pointers of this inner node. See the comment above
// LeafNode.keys and LeafNode.rids in LeafNode.java for a warning on the
// difference between the keys and children here versus the keys and children
// stored on disk. `keys` is always stored in ascending order.
private List<DataBox> keys;
private List<Long> children;
// Constructors ////////////////////////////////////////////////////////////
/**
* Construct a brand new inner node.
*/
InnerNode(BPlusTreeMetadata metadata, BufferManager bufferManager, List<DataBox> keys,
List<Long> children, LockContext treeContext) {
this(metadata, bufferManager, bufferManager.fetchNewPage(treeContext, metadata.getPartNum()),
keys, children, treeContext);
}
/**
* Construct an inner node that is persisted to page `page`.
*/
private InnerNode(BPlusTreeMetadata metadata, BufferManager bufferManager, Page page,
List<DataBox> keys, List<Long> children, LockContext treeContext) {
try {
assert (keys.size() <= 2 * metadata.getOrder());
assert (keys.size() + 1 == children.size());
this.metadata = metadata;
this.bufferManager = bufferManager;
this.treeContext = treeContext;
this.page = page;
this.keys = new ArrayList<>(keys);
this.children = new ArrayList<>(children);
sync();
} finally {
page.unpin();
}
}
// Core API ////////////////////////////////////////////////////////////////
// See BPlusNode.get.
@Override
public LeafNode get(DataBox key) {
// TODO(proj2): implement
int whereToRetrieve = numLessThanEqual(key, this.keys);
return getChild(whereToRetrieve).get(key);
}
// See BPlusNode.getLeftmostLeaf.
@Override
public LeafNode getLeftmostLeaf() {
assert(children.size() > 0);
// TODO(proj2): implement
return getChild(0).getLeftmostLeaf();
}
// See BPlusNode.put.
@Override
public Optional<Pair<DataBox, Long>> put(DataBox key, RecordId rid) {
// TODO(proj2): implement
int currentIndex = numLessThanEqual(key, keys);
Optional<Pair<DataBox, Long>> placedPair = get(key).put(key, rid);
int maxKeys = this.metadata.getOrder() * 2;
if (!placedPair.isPresent()) {
sync();
return Optional.empty();
}
this.keys.add(currentIndex, placedPair.get().getFirst());
this.children.add(currentIndex + 1, placedPair.get().getSecond());
// case 1: no need to split
if (this.keys.size() <= maxKeys) {
sync();
return Optional.empty();
}
// case 2: need to split
int order = this.metadata.getOrder();
int newKeysSize = this.keys.size();
List<DataBox> newNodeKeys = this.keys.subList(order + 1, newKeysSize);
DataBox splitKey = this.keys.get(order);
this.keys = this.keys.subList(0, order);
List<Long> newNodeChildren = this.children.subList(order + 1, newKeysSize);
this.children = this.children.subList(0, order);
InnerNode newInnerNode = new InnerNode(this.metadata, this.bufferManager, newNodeKeys, newNodeChildren, this.treeContext);
Optional<Pair<DataBox, Long>> newPlacedPair = Optional.of(new Pair<>(splitKey, newInnerNode.getPage().getPageNum()));
newInnerNode.sync();
sync();
return newPlacedPair;
}
// See BPlusNode.bulkLoad.
@Override
public Optional<Pair<DataBox, Long>> bulkLoad(Iterator<Pair<DataBox, RecordId>> data,
float fillFactor) {
// TODO(proj2): implement
return Optional.empty();
}
// See BPlusNode.remove.
@Override
public void remove(DataBox key) {
// TODO(proj2): implement
return;
}
// Helpers /////////////////////////////////////////////////////////////////
@Override
public Page getPage() {
return page;
}
private BPlusNode getChild(int i) {
long pageNum = children.get(i);
return BPlusNode.fromBytes(metadata, bufferManager, treeContext, pageNum);
}
private void sync() {
page.pin();
try {
Buffer b = page.getBuffer();
byte[] newBytes = toBytes();
byte[] bytes = new byte[newBytes.length];
b.get(bytes);
if (!Arrays.equals(bytes, newBytes)) {
page.getBuffer().put(toBytes());
}
} finally {
page.unpin();
}
}
// Just for testing.
List<DataBox> getKeys() {
return keys;
}
// Just for testing.
List<Long> getChildren() {
return children;
}
/**
* Returns the largest number d such that the serialization of an InnerNode
* with 2d keys will fit on a single page.
*/
static int maxOrder(short pageSize, Type keySchema) {
// A leaf node with n entries takes up the following number of bytes:
//
// 1 + 4 + (n * keySize) + ((n + 1) * 8)
//
// where
//
// - 1 is the number of bytes used to store isLeaf,
// - 4 is the number of bytes used to store n,
// - keySize is the number of bytes used to store a DataBox of type
// keySchema, and
// - 8 is the number of bytes used to store a child pointer.
//
// Solving the following equation
//
// 5 + (n * keySize) + ((n + 1) * 8) <= pageSizeInBytes
//
// we get
//
// n = (pageSizeInBytes - 13) / (keySize + 8)
//
// The order d is half of n.
int keySize = keySchema.getSizeInBytes();
int n = (pageSize - 13) / (keySize + 8);
return n / 2;
}
/**
* Given a list ys sorted in ascending order, numLessThanEqual(x, ys) returns
* the number of elements in ys that are less than or equal to x. For
* example,
*
* numLessThanEqual(0, Arrays.asList(1, 2, 3, 4, 5)) == 0
* numLessThanEqual(1, Arrays.asList(1, 2, 3, 4, 5)) == 1
* numLessThanEqual(2, Arrays.asList(1, 2, 3, 4, 5)) == 2
* numLessThanEqual(3, Arrays.asList(1, 2, 3, 4, 5)) == 3
* numLessThanEqual(4, Arrays.asList(1, 2, 3, 4, 5)) == 4
* numLessThanEqual(5, Arrays.asList(1, 2, 3, 4, 5)) == 5
* numLessThanEqual(6, Arrays.asList(1, 2, 3, 4, 5)) == 5
*
* This helper function is useful when we're navigating down a B+ tree and
* need to decide which child to visit. For example, imagine an index node
* with the following 4 keys and 5 children pointers:
*
* +---+---+---+---+
* | a | b | c | d |
* +---+---+---+---+
* / | | | \
* 0 1 2 3 4
*
* If we're searching the tree for value c, then we need to visit child 3.
* Not coincidentally, there are also 3 values less than or equal to c (i.e.
* a, b, c).
*/
static <T extends Comparable<T>> int numLessThanEqual(T x, List<T> ys) {
int n = 0;
for (T y : ys) {
if (y.compareTo(x) <= 0) {
++n;
} else {
break;
}
}
return n;
}
static <T extends Comparable<T>> int numLessThan(T x, List<T> ys) {
int n = 0;
for (T y : ys) {
if (y.compareTo(x) < 0) {
++n;
} else {
break;
}
}
return n;
}
// Pretty Printing /////////////////////////////////////////////////////////
@Override
public String toString() {
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < keys.size(); ++i) {
sb.append(children.get(i)).append(" ").append(keys.get(i)).append(" ");
}
sb.append(children.get(children.size() - 1)).append(")");
return sb.toString();
}
@Override
public String toSexp() {
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < keys.size(); ++i) {
sb.append(getChild(i).toSexp()).append(" ").append(keys.get(i)).append(" ");
}
sb.append(getChild(children.size() - 1).toSexp()).append(")");
return sb.toString();
}
/**
* An inner node on page 0 with a single key k and two children on page 1 and
* 2 is turned into the following DOT fragment:
*
* node0[label = "<f0>|k|<f1>"];
* ... // children
* "node0":f0 -> "node1";
* "node0":f1 -> "node2";
*/
@Override
public String toDot() {
List<String> ss = new ArrayList<>();
for (int i = 0; i < keys.size(); ++i) {
ss.add(String.format("<f%d>", i));
ss.add(keys.get(i).toString());
}
ss.add(String.format("<f%d>", keys.size()));
long pageNum = getPage().getPageNum();
String s = String.join("|", ss);
String node = String.format(" node%d[label = \"%s\"];", pageNum, s);
List<String> lines = new ArrayList<>();
lines.add(node);
for (int i = 0; i < children.size(); ++i) {
BPlusNode child = getChild(i);
long childPageNum = child.getPage().getPageNum();
lines.add(child.toDot());
lines.add(String.format(" \"node%d\":f%d -> \"node%d\";",
pageNum, i, childPageNum));
}
return String.join("\n", lines);
}
// Serialization ///////////////////////////////////////////////////////////
@Override
public byte[] toBytes() {
// When we serialize an inner node, we write:
//
// a. the literal value 0 (1 byte) which indicates that this node is not
// a leaf node,
// b. the number n (4 bytes) of keys this inner node contains (which is
// one fewer than the number of children pointers),
// c. the n keys, and
// d. the n+1 children pointers.
//
// For example, the following bytes:
//
// +----+-------------+----+-------------------------+-------------------------+
// | 00 | 00 00 00 01 | 01 | 00 00 00 00 00 00 00 03 | 00 00 00 00 00 00 00 07 |
// +----+-------------+----+-------------------------+-------------------------+
// \__/ \___________/ \__/ \_________________________________________________/
// a b c d
//
// represent an inner node with one key (i.e. 1) and two children pointers
// (i.e. page 3 and page 7).
// All sizes are in bytes.
assert (keys.size() <= 2 * metadata.getOrder());
assert (keys.size() + 1 == children.size());
int isLeafSize = 1;
int numKeysSize = Integer.BYTES;
int keysSize = metadata.getKeySchema().getSizeInBytes() * keys.size();
int childrenSize = Long.BYTES * children.size();
int size = isLeafSize + numKeysSize + keysSize + childrenSize;
ByteBuffer buf = ByteBuffer.allocate(size);
buf.put((byte) 0);
buf.putInt(keys.size());
for (DataBox key : keys) {
buf.put(key.toBytes());
}
for (Long child : children) {
buf.putLong(child);
}
return buf.array();
}
/**
* Loads an inner node from page `pageNum`.
*/
public static InnerNode fromBytes(BPlusTreeMetadata metadata,
BufferManager bufferManager, LockContext treeContext, long pageNum) {
Page page = bufferManager.fetchPage(treeContext, pageNum);
Buffer buf = page.getBuffer();
byte nodeType = buf.get();
assert(nodeType == (byte) 0);
List<DataBox> keys = new ArrayList<>();
List<Long> children = new ArrayList<>();
int n = buf.getInt();
for (int i = 0; i < n; ++i) {
keys.add(DataBox.fromBytes(buf, metadata.getKeySchema()));
}
for (int i = 0; i < n + 1; ++i) {
children.add(buf.getLong());
}
return new InnerNode(metadata, bufferManager, page, keys, children, treeContext);
}
// Builtins ////////////////////////////////////////////////////////////////
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof InnerNode)) {
return false;
}
InnerNode n = (InnerNode) o;
return page.getPageNum() == n.page.getPageNum() &&
keys.equals(n.keys) &&
children.equals(n.children);
}
@Override
public int hashCode() {
return Objects.hash(page.getPageNum(), keys, children);
}
}
Editor is loading...