MIT6.830-Lab2

simpleDB项目地址

实验部分

实验1

  • Predicate类

    用来存放对表记录进行条件过滤的信息(要过滤字段的序号,具体的比较规则,用来比较的字段),其内部的枚举类Op就是比较规则类,filter()方法的实现使用Field接口中的compare()即可。

  • JoinPredicate类

    用来存放两表的记录进行连接的信息(两表之间要连接字段的序号,连接规则),其实现和Predicate基本一致。

  • Filter类

    真正实现过滤操作,内部含有Predicate类对象和OpIterator数组(OpIterator是每个操作类都要实现的接口,用来对生成的结果集进行遍历,数组中元素指向SeqScan实例对象),它继承了Opreator类(该类为抽象类,实现了OpIteratornext()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类

    JoinFilter类似,重点同样在于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类

    它是整形字段聚合方法的实现类,其中gbFiledaFieldgbFieldType成员变量分别代表用于分组字段的序号、聚合字段的序号、分组字段的类型,成员变量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类

    它是对IntegerAggregatorStringAggregator使用的一个封装类,成员变量opIterators数组指向待聚合记录的迭代器,aggregator指向具体的聚合器(IntegerAggregatorStringAggregator类型),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中调用HeapPagedeleteTuple()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类
    最终BufferPooldeleteTuple()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");
    }
posted @   rockdow  阅读(13)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示