MIT6.830-Lab2
实验部分
实验1
-
Predicate类
用来存放对表记录进行条件过滤的信息(要过滤字段的序号,具体的比较规则,用来比较的字段),其内部的枚举类
Op
就是比较规则类,filter()
方法的实现使用Field
接口中的compare()
即可。 -
JoinPredicate类
用来存放两表的记录进行连接的信息(两表之间要连接字段的序号,连接规则),其实现和
Predicate
基本一致。 -
Filter类
真正实现过滤操作,内部含有
Predicate类
对象和OpIterator
数组(OpIterator
是每个操作类都要实现的接口,用来对生成的结果集进行遍历,数组中元素指向SeqScan
实例对象),它继承了Opreator类
(该类为抽象类,实现了OpIterator
的next()
和hasNext()
方法)以简化编码。
重点在fetchNext()
方法的实现,可以通过OpIterator
数组中的对象逐条取出表记录,并用Predicate
对象判断该条记录是否符合条件:
protected Tuple fetchNext() throws NoSuchElementException,
TransactionAbortedException, DbException {
Tuple next = null;
while(opIterator[0].hasNext()){
next = opIterator[0].next();
if(next == null) {
break;
}
else {
if(predicate.filter(next))
break;
else
// 如果不匹配predicate的规则,那么要把next置为null,避免将最后一条不符合规则的记录返回,导致出错
next = null;
}
}
return next;
}
-
Join类
Join
和Filter
类似,重点同样在于fetchNext()
方法,我使用了简单的嵌套循环方式实现,left
变量存放join连接中左表的记录,right
变量存放右表的记录,leftThreadLocal
变量则是记录当前左表遍历到哪条记录(使用嵌套循环时,以左表的某条记录为基准,到右表中查找符合连接条件的记录,当寻找到右表中一条该类记录后,fetchNext()
方法就要返回,但此时右表很可能还未遍历完,因此需要将左表的当前记录保存下来,用于右表剩余数据的遍历)
protected Tuple fetchNext() throws TransactionAbortedException, DbException {
Tuple left = null;
Tuple right = null;
// 判断 opIterators[0].hasNext() || opIterators[1].hasNext() 是让 opIterators[0]拿出最后一个元素后可以再遍历一遍 opIterators[1]中的元素
outer:while(opIterators[0].hasNext() || opIterators[1].hasNext()){
if(initial==false || !opIterators[1].hasNext()){
initial = true;
if(!opIterators[1].hasNext())
opIterators[1].rewind();
leftThreadLocal.set(opIterators[0].next());
}
left = leftThreadLocal.get();
right = null;
if(left != null){
while (opIterators[1].hasNext()){
right = opIterators[1].next();
if(right != null){
if(joinPredicate.filter(left,right)){
break outer;
}else {
right = null;
}
}
}
}
}
if(left == null || right == null){
return null;
}else{
Tuple tuple = new Tuple(this.getTupleDesc());
Iterator<Field> lIter = left.fields();
Iterator<Field> rIter = right.fields();
int idx = 0;
while (lIter.hasNext()){
tuple.setField(idx++,lIter.next());
}
while (rIter.hasNext()){
tuple.setField(idx++,rIter.next());
}
return tuple;
}
}
实验2
-
Aggregator类
它是每个聚合函数都要实现的接口,内部的
Op类
规定了具体的聚合规则,mergeTupleIntoGroup()
方法用于将表记录按照group by
字段分组,用于之后的聚合计算,iterator()
方法返回OpIterator
对象,用来遍历聚合结果。 -
IntegerAggregator类
它是整形字段聚合方法的实现类,其中
gbFiled
、aField
、gbFieldType
成员变量分别代表用于分组字段的序号、聚合字段的序号、分组字段的类型,成员变量Map<Field,ArrayList<IntField>> map
用来记录每个分组含有哪些记录的聚合字段(SQL查询中,如果存在聚合和分组操作,结果集中只能包含聚合字段和分组字段)。先看mergeTupleIntoGroup()
实现,整体思路就是如果没有分组字段,那么就将所有记录的聚合字段分为一组,如果存在分组字段,那么就按照分组字段将记录的聚合字段存入不同的组:
public void mergeTupleIntoGroup(Tuple tup) {
if(gbFiled == Aggregator.NO_GROUPING){
StringField noGB = new StringField("NoGB", 4);
if(map.isEmpty()){
ArrayList<IntField> intFields = new ArrayList<>();
intFields.add((IntField) tup.getField(aField));
map.put(noGB,intFields);
}else {
map.get(noGB).add((IntField) tup.getField(aField));
}
}else {
if(!map.containsKey(tup.getField(gbFiled))){
ArrayList<IntField> intFields = new ArrayList<>();
intFields.add((IntField) tup.getField(aField));
map.put(tup.getField(gbFiled),intFields);
}else {
ArrayList<IntField> arr = map.get(tup.getField(gbFiled));
arr.add((IntField) tup.getField(aField));
}
}
}
接着是iterator()
方法实现,我选择在调用OpIterator
对象的open()
方法时,将Map<Field,ArrayList<IntField>> map
中的数据处理成对应聚合函数的格式:
public void open() throws DbException, TransactionAbortedException {
this.idx = 0;
arr = new ArrayList<>();
if(gbFiled == Aggregator.NO_GROUPING){
this.tupleDesc = new TupleDesc(new Type[]{Type.INT_TYPE});
Tuple tuple = new Tuple(this.tupleDesc);
ArrayList<IntField> list = map.get(new StringField("NoGB", 4));
tuple.setField(0,list.get(0));
this.arr.add(tuple);
int sum = list.get(0).getValue();
for (int i=1;i< list.size();i++){
IntField tupleField = (IntField) tuple.getField(0);
IntField listField = list.get(i);
switch (op){
case MIN:
if(tupleField.compare(Predicate.Op.GREATER_THAN,listField))
tuple.setField(0,listField);
break;
case MAX:
if(tupleField.compare(Predicate.Op.LESS_THAN,listField))
tuple.setField(0,listField);
break;
case AVG:
case SUM:
sum += listField.getValue();
break;
case COUNT:
break;
default:
throw new DbException(op+" is unimplemented");
}
}
if(op == SUM){
tuple.setField(0,new IntField(sum));
}
if(op == AVG){
tuple.setField(0,new IntField(sum/ list.size()));
}
if(op == Op.COUNT){
tuple.setField(0,new IntField(list.size()));
}
}else{
if(gbFieldType == Type.STRING_TYPE)
this.tupleDesc = new TupleDesc(new Type[]{Type.STRING_TYPE,Type.INT_TYPE});
else
this.tupleDesc = new TupleDesc(new Type[]{Type.INT_TYPE,Type.INT_TYPE});
for(Field field: map.keySet()){
ArrayList<IntField> intFields = map.get(field);
Tuple tuple = new Tuple(this.tupleDesc);
tuple.setField(0,field);
tuple.setField(1, intFields.get(0));
this.arr.add(tuple);
int sum = intFields.get(0).getValue();
for (int i=1;i<intFields.size();i++){
IntField intField = intFields.get(i);
IntField tupleField = (IntField) tuple.getField(1);
switch (op){
case MIN:
if(tupleField.compare(Predicate.Op.GREATER_THAN,intField))
tuple.setField(1,intField);
break;
case MAX:
if(tupleField.compare(Predicate.Op.LESS_THAN,intField))
tuple.setField(1,intField);
break;
case AVG:
case SUM:
sum += intField.getValue();
break;
case COUNT:
break;
default:
throw new DbException(op+" is unimplemented");
}
}
if(op == SUM){
tuple.setField(1,new IntField(sum));
}
if(op == AVG){
tuple.setField(1,new IntField(sum/intFields.size()));
}
if(op == Op.COUNT){
tuple.setField(1,new IntField(intFields.size()));
}
}
}
this.isOpen = true;
}
-
StringAggregator类
实现类似
IntegerAggregator
,略过 -
Aggregate类
它是对
IntegerAggregator
和StringAggregator
使用的一个封装类,成员变量opIterators
数组指向待聚合记录的迭代器,aggregator
指向具体的聚合器(IntegerAggregator
或StringAggregator
类型),aggIterator
是该聚合器的迭代器,用来遍历聚合结果。重要的是open()
方法,先将待聚合记录分组,再处理数据格式:
public void open() throws NoSuchElementException, DbException,
TransactionAbortedException {
super.open();
opIterators[0].open();
while (opIterators[0].hasNext()){
aggregator.mergeTupleIntoGroup(opIterators[0].next());
}
this.aggIterator = aggregator.iterator();
this.aggIterator.open();
}
实验3
HeapPage类
先完成该类中的deleteTuple()
、insertTuple()
方法。根据文档可知,增删记录要修改页中header
数组对应的的标志位,因此要用到markSlotUsed()
方法,它可以将序号为i的记录的标志位置为0或1。
private void markSlotUsed(int i, boolean value) {
int nthHeaderByte = i/8;
int nthBit = i%8;
if(value){
if(!isSlotUsed(i))
header[nthHeaderByte] = (byte) (header[nthHeaderByte] ^ (1<<nthBit));
}else {
if(isSlotUsed(i))
header[nthHeaderByte] = (byte) (header[nthHeaderByte] ^ (1<<nthBit));
}
}
这样deleteTuple()
的实现就简单了,将根据记录中存储的序号作为参数传递给markSlotUsed()
,再将tuples
数组中对应位置置空即可。
public void deleteTuple(Tuple t) throws DbException {
if(t==null || t.getRecordId()==null || t.getRecordId().getPageId()==null)
throw new DbException("the tuple is illegal");
if(!pid.equals(t.getRecordId().getPageId()) || !isSlotUsed(t.getRecordId().getTupleNumber()))
throw new DbException("the tuple is not in the page or empty");
markSlotUsed(t.getRecordId().getTupleNumber(),false);
tuples[t.getRecordId().getTupleNumber()] = null;
}
insertTuple()
思想类似,不过它要先遍历tuples
数组找到空位,再进行插入,别忘记修改该记录对应的Record
信息(插入的页号和序号改变了)。
public void insertTuple(Tuple t) throws DbException {
if(t==null || t.getTupleDesc()==null)
throw new DbException("the tuple is illegal");
if(getNumEmptySlots()==0 || !td.equals(t.getTupleDesc()))
throw new DbException("the page is full or tupleDesc is mismatch");
int idx = 0;
while(idx < tuples.length){
if(tuples[idx] == null)
break;
idx++;
}
RecordId recordId = new RecordId(pid, idx);
t.setRecordId(recordId);
markSlotUsed(idx,true);
tuples[idx] = t;
}
HeapFile类
HeapFile
中调用HeapPage
的deleteTuple()
、insertTuple()
来实现自己的增删方法,其deleteTuple()
如下,返回的是脏页(之后的Lab中会用):
public ArrayList<Page> deleteTuple(TransactionId tid, Tuple t) throws DbException,
TransactionAbortedException {
ArrayList<Page> pages = new ArrayList<>(1);
HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, t.getRecordId().getPageId(), Permissions.READ_WRITE);
page.deleteTuple(t);
pages.add(page);
return pages;
}
insertTuple()
则较为复杂,它需要遍历该文件中所有的数据页,以查找留有空位的页并插入记录,如果所有的页都满了,则要新建一个页并将记录插入其中,新建页要及时写入磁盘(测试的时候会判断页的数目)。
public List<Page> insertTuple(TransactionId tid, Tuple t)
throws DbException, IOException, TransactionAbortedException {
// 返回的pages会被标记成脏页,留待BufferPool统一刷盘
ArrayList<Page> pages = new ArrayList<>(1);
for(int pageNo=0;pageNo<this.numPages();pageNo++){
HeapPageId pageId = new HeapPageId(getId(), pageNo);
HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, pageId, Permissions.READ_WRITE);
if(page.getNumEmptySlots() > 0){
page.insertTuple(t);
pages.add(page);
return pages;
}else{
//因为未修改页的内容,所以虽然违背了2pl,也可以释放锁
Database.getBufferPool().unsafeReleasePage(tid,pageId);
}
}
// 走到这步说明已有的page不满足要求,需要创建新页,该新创建的页要立刻写入磁盘(单元测试的意思是这样)
// 使用空的字节数组创建HeapPage,会划分好header和tuple部分,并初始化为未使用
HeapPage newPage = new HeapPage(new HeapPageId(getId(), this.numPages()), HeapPage.createEmptyPageData());
newPage.insertTuple(t);
this.writePage(newPage);
// 新建的页加入缓存
Database.getBufferPool().getPage(tid, newPage.getId(), Permissions.READ_WRITE);
pages.add(newPage);
return pages;
}
BufferPool类
最终BufferPool
的deleteTuple()
、insertTuple()
调用HeapPage
的增删方法获取脏页(使用markDirty()
将返回的页标记为脏页),将脏页更新到缓存中。
实验4
Insert类
该类是插入操作符,继承了Operator
,通过调用BufferPool
的插入方法,将迭代器指向的记录全部插入到某个文件中,核心方法是fetchNext()
。
protected Tuple fetchNext() throws TransactionAbortedException, DbException {
if(!isCalled){
isCalled = true;
int numInserted = 0;
while(opIterators[0].hasNext()){
try {
Database.getBufferPool().insertTuple(tid,tableId,opIterators[0].next());
numInserted++;
}catch (IOException e){
e.printStackTrace();
System.exit(0);
}
}
Tuple tuple = new Tuple(new TupleDesc(new Type[]{Type.INT_TYPE}));
tuple.setField(0,new IntField(numInserted));
return tuple;
}else {
return null;
}
}
Delete类
与Insert类
思路类似,略过
实验5
BufferPool类
本实验要求实现缓存驱逐策略。flushPage()
方法将脏页写入磁盘,并且将缓存中该页标记为clean
状态。discardPage()
方法将缓存中的页丢弃。evictPage()
真正实现缓存驱逐策略,我的思路为遍历缓存,优先驱逐clean
页,如果所有页都是脏页,再抛出异常(后面的Lab4有要求不能驱逐脏页,flushPage()
会在事务提交时被调用)。
private synchronized void evictPage() throws DbException {
for(Map.Entry<PageId, Page> entry : map.entrySet()) {
if(entry.getValue().isDirty() == null) {
discardPage(entry.getKey());
lockManager.removePageLock(entry.getKey());
return;
}
}
// 到这说明都是dirty页
throw new DbException("the bufferPool is full of dirty page");
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步