mit数据库实验2

参考文档

代码实现

讲解

目录

  • 过滤、连接、orderby
  • 聚集函数
  • 元组插入删除
  • 页面替换eviction

实验一:Join和filter

predicate:

三个域:

  • filed:域的id
  • op:操作符
  • operand:操作数

函数实现:

  • filter()
  • toString()
    public boolean filter(Tuple t) {
    
    
        // some code goes here
        return t.getField(this.field).compare(op, this.operand);        // 这个compare已经由某一个域给定义好了int和string的比较规则
    }

joinpredicte

  • 两个元组内字段比较,用于join运算

域:

  • 两个元组的域
  • 操作符

函数

  • filter()
  public boolean filter(Tuple t1, Tuple t2) {
    
    
        // some code goes here
        return t1.getField(this.field_a).compare(op, t2.getField(this.field_b));
    }
    

filter

  • 进行过滤操作
  • super.close()和open()
  • fetchNext()
    protected Tuple fetchNext() throws NoSuchElementException,
            TransactionAbortedException, DbException {
    
    
        // some code goes here
        while(child.hasNext()) {
    
    
            Tuple tuple = child.next();
            if (this.p.filter(tuple))                           // 通过过滤器看看是否满足条件
                return tuple;
        }
        return null;
    }

join

域:

  • private final JoinPredicate joinPredicate; 双元组比较
  • private OpIterator child1; 元组迭代器1
  • private OpIterator child2; 元组迭代器2
  • private Tuple t; 临时元组,保存上次迭代用的 child1 的 Tuple

这个主要用于下次遍历的时候,继续使用当前元组,因为可能有多个符合条件的结果,也就是一对多,所以t1不能够直接跳到下一条,使用临时值保存当前元组。

函数:

  • fetchNext()
protected Tuple fetchNext() throws TransactionAbortedException, DbException {
    
    
    // some code goes here
    // t 的意义就是 笛卡尔积, 一个可能对多个相等
    while (child1.hasNext() || t != null){
    
    
        if(child1.hasNext() && t == null){
    
    
            t = child1.next();
        }
        while(child2.hasNext()){
    
    
            Tuple t2 = child2.next();
            if(joinPredicate.filter(t, t2)){
    
    
                TupleDesc td1 = t.getTupleDesc();
                TupleDesc td2 = t2.getTupleDesc();
                // 合并
                TupleDesc tupleDesc = TupleDesc.merge(td1, td2);
                Tuple newTuple = new Tuple(tupleDesc);
                // 设置路径, 暂时不先设置
                // newTuple.setRecordId(t.getRecordId());
                int i = 0;
                for (; i < td1.numFields(); i++) {
    
    
                    newTuple.setField(i, t.getField(i));
                }
                for (int j = 0; j < td2.numFields(); j++) {
    
    
                    newTuple.setField(i + j, t2.getField(j));
                }
                // 遍历完,t2重头,t走向下一个
                if(!child2.hasNext()){
    
    
                    child2.rewind();
                    t = null;
                }
                return newTuple;
            }
        }
        // 重置 child2
        child2.rewind();
        t = null;
    }
    return null;
}

实验二:Aggregates

Aggregator

  • child :表迭代器

  • afield:需要聚合的域id

  • gfield:需要分组的域id

  • gfieldType:需要分组的域类型,因为没有表信息,所以只能通过父类传递。

  • AggHandler:自定义的handler,用来实现不同的聚合函数。由op来决定创建什么类型的聚合函数。

    • 保存答案的map{key:分组,value:每组聚合函数的结果}
    • handle方法,抽象方法,实现具体的聚集函数逻辑。

方法

  • mergeTotuple:通过自定义的handler,更新handler中的结果集合。

  • iterator:迭代器,将结果集合转化成tuple的形式。然后返回tuple列表的迭代器。

    • tuple表头是{groupVal,aggVal}
    • tuple内容是map中的键值对

代码

    public OpIterator iterator() {
    
    
        // some code goes here
//        throw new
//        UnsupportedOperationException("please implement me for lab2");
        Map<Field, IntField> ans = handler.ans;
        List<Tuple>tuples = new ArrayList<>();
        TupleDesc tupleDesc;                                        // 创建迭代器的时候,需要用到
        if (gfield == NO_GROUPING) {
    
                                  // 如果没有group
            // 创建tuple
            tupleDesc = new TupleDesc(new Type[]{
    
    Type.INT_TYPE}, new String[]{
    
    "aggregateVal"});
            Tuple tuple = new Tuple(tupleDesc);
            // 把查询到的结果放入tuple中
            tuple.setField(0, ans.get(null));
            // 不要忘了把创建的tuple放入到结果集中
            tuples.add(tuple);
        }
        else {
    
                                                         // 如果含有group
            tupleDesc = new TupleDesc(new Type[]{
    
    this.gfieldtype, Type.INT_TYPE}, new String[]{
    
    "groupVal", "aggregateVal"});
            for (Map.Entry<Field, IntField> entry : ans.entrySet()) {
    
    
                Tuple tuple = new Tuple(tupleDesc);
                tuple.setField(0, entry.getKey());
                tuple.setField(1, entry.getValue());
                tuples.add(tuple);
            }
        }
        return new TupleIterator(tupleDesc, tuples);
    }
    public void mergeTupleIntoGroup(Tuple tup) {
    
    
        // some code goes here
        // 获取对应域的值
        IntField afield = (IntField) tup.getField(this.afield);
        Field gfield = this.gfield == NO_GROUPING ? null : tup.getField(this.gfield);
        handler.handle(gfield, afield);
    }
private abstract class AggHandler {
    
    
        private Map<Field, IntField>ans;
        abstract void handle(Field gfield, IntField afield);            // group by不知道什么类型,但是聚集函数得到的结果一定是int
        public AggHandler() {
    
    
            ans = new HashMap<>();
        }
        public Map<Field, IntField> getAns() {
    
    
            return this.ans;
        }
    }
    private class MinHandler extends AggHandler {
    
    
        @Override
        void handle(Field gfield, IntField afield) {
    
    
            int x = afield.getValue();
            Map<Field, IntField>ans = this.getAns();
            if (ans.containsKey(gfield)) {
    
    
                ans.put(gfield, new IntField(Math.min(x, ans.get(gfield).getValue())));
            }
            else ans.put(gfield, new IntField(x));
        }
    }
public IntegerAggregator(int gbfield, Type gbfieldtype, int afield, Op what) {
    
    
        // some code goes here
        this.gfield = gbfield;
        this.afield = afield;
        this.gfieldtype = gbfieldtype;
        this.op = what;
        switch (what) {
    
    
            case MIN:
                this.handler = new MinHandler(); break;
            case AVG:
                this.handler = new AvgHandler(); break;
            case SUM:
                this.handler = new SumHandler(); break;
            case COUNT:
                this.handler = new CountHandler(); break;
            case MAX:
                this.handler = new MaxHandler(); break;
            default:
                throw new IllegalArgumentException("聚合器不支持当前运算符");
        }
    }

Aggregates

域:

  • child:表迭代器入口
  • afield:聚合的域id
  • gfield:分组的域Id
  • aop: 聚合函数的id
  • aggregator:集合器,上面的一种,由afield决定使用什么类型的
  • iterator:结果集合的迭代器

方法:

  • 构造函数
  • getTupleDesc():返回形如max(score)这种的表头。分是否有gfeild的来决定列数

代码:

  public TupleDesc getTupleDesc() {
    
    
        // some code goes here
//        String afiledName = this.aop.name() + "("  + this.aggregateFieldName() + ")";       // max(id);
//        Type aType = this.child.getTupleDesc().getFieldType(this.afield);
//        if (this.groupFieldName() != null) {
    
    
//            String gfiledName = this.groupFieldName();
//            Type gType = this.child.getTupleDesc().getFieldType(this.gfield);
//            return new TupleDesc(new Type[]{gType, aType},new String[]{gfiledName, afiledName});
//        }
//        else {
    
    
//            return new TupleDesc(new Type[]{aType}, new String[]{afiledName});
//        }
        List<Type>types = new ArrayList<>();
        List<String>names = new ArrayList<>();
        if (groupFieldName() != null) {
    
    
            types.add(this.child.getTupleDesc().getFieldType(this.gfield));
            names.add(groupFieldName());
        }
        types.add(this.child.getTupleDesc().getFieldType(this.afield));
        names.add(this.aop.name() + "(" + aggregateFieldName() + ")");          // 类似于max(id), AVG(score)这种
        if (this.aop.equals(Aggregator.Op.SUM_COUNT)) {
    
                             // 如果有
            types.add(Type.INT_TYPE);
            names.add("COUNT");
        }
        return new TupleDesc(types.toArray(new Type[types.size()]), names.toArray(new String[names.size()]));
    }
    // 这里可以使用工厂模式进行改写
    public Aggregate(OpIterator child, int afield, int gfield, Aggregator.Op aop) {
    
    
        // some code goes here
        this.child = child;
        this.afield = afield;
        this.gfield = gfield;
        this.aop = aop;
        Type gfieldtype;
        if (gfield == NO_GROUPING) gfieldtype = null;
        else gfieldtype = this.child.getTupleDesc().getFieldType(this.gfield);
        if (this.child.getTupleDesc().getFieldType(afield).equals(Type.STRING_TYPE))
            this.aggregator = new StringAggregator(gfield, this.child.getTupleDesc().getFieldType(this.gfield), afield, aop);
        else this.aggregator = new IntegerAggregator(gfield, gfieldtype, afield, aop);
//        desc = this.getTupleDesc();
    }

实验三:HeapPage和HeapFile,bufferpool完善

heapPage:

增加事务id

插入和删除

heapFile

插入和删除

buffer pool

  • flush

  • insertTuple:这个逻辑在BufferPoolWriteTest中有,但是没有从bufferpool中获取,修改test中的insert内容

    • 首先,如果有可以插入的就插入
    • 其次,没有可以插入的,创建新页面,插入文件
    • 最后,从缓存中获取页面。
      • 缓存中页面的逻辑是,如果没有从文件中读取,否则直接返回
      • 另外缓存中的get需要重构,双向链表,缓存满了,末尾淘汰。
  • DeleteTuple

  • iterator中的逻辑出错了

实验四:插入和删除

  • 事务id
  • 插入的迭代器
  • 插入的表
  • 结果tupleDesc
  • 插入标记

方法

直接调用Bufferpool.insert()和delete方法就行了。

实验五:淘汰策略

双向链表实现LRU。

  1. 每次使用,将页面放头。

实验六:连接以及过滤查询

  1. 创建文件
  2. 放入内容,注意换行
  3. 执行测试文件

实验7:查询解析器使用

  1. 创建文件
  2. 执行命令转换,前提是编译ant通过
  3. 最后执行命令,进入命令行
  4. 输入查询,得到结果。

问题

  1. recordId在Join连接之后,为什么设置成t1的呢?

    1. 应该设置成临时表的
    2. 但是没有临时表
  2. 为什么继承于Operator而不是Implemnent OpIterator

    1. 因为next和hasNext()是重复的方法
  3. 为什么open和close需要super.open和super.close呢?

    1. 因为这两个方法是重写于父类。判断的逻辑在父类的hasNext()和next()
    2. 所以要先改父类的状态,从而通过上面的两个办法,之后在修改自己的方法。
  4. 创建新的tuple的时候recordId应该是多少?

  5. setchildren的意义在哪里?

  6. sum_count如何实现的?另外为什么sum_count需要在desc中新增一列?

    1. 因为两个操作,所以新增了一列。
  7. 插入和删除元组的时候,是否应该立即写文件呢?如果不是,什么时候进行写文件呢?

  8. 为什么在bufferpool中打脏写的标记?不是在DbFile的时候打?

  9. 末尾淘汰的时候,是否需要刷盘呢?flush和删除是两个操作。这两个操作由更高层的进行控制。如果需要立即刷盘,就delete(),flush,否则就不需要。

  10. iterator中的逻辑,应该是本页没有,找第一个有的,如果都没有才结束。

11. 编码问题

修改build中的copile如下

target name="compile" description="Compile code">
        <javac srcdir="${src}/java" destdir="${build.src}" debug="on" deprecation="on" optimize="off" includes="**">
            <!--给编译器指定编码,防止出现:"警告: 编码 GBK 的不可映射字符"-->
            <compilerarg line="-encoding UTF-8 "/>
            <classpath refid="classpath.base" />
        </javac>
        <Compile srcdir="${src}/java" destdir="${build.src}">
            <classpath refid="classpath.base"/>
        </Compile>
    <copy todir="${build}" flatten="true">
        <fileset dir="${src}">
            <include name="bin/*.sh"/>
        </fileset>
    </copy>
    </target>

总结

  1. debug的总结:由于碰见了no_group_avg的问题。进行代码调试,对自己写的代码打端点,tuple忘了加入到tuples中。
  2. 插入和删除的时候,一定及时更新缓存。而不用立刻写文件。所以有了脏页。

猜你喜欢

转载自blog.csdn.net/fuzekun/article/details/129282283