OceanBase大赛第四届-初赛记录-2.date

· · 个人记录

date

在现有功能上实现日期类型字段。

当前已经支持了int、char、float类型,在此基础上实现date类型的字段。date测试可能超过2038年2月,也可能小于1970年1月1号。注意处理非法的date输入,需要返回FAILURE。

这道题目需要从词法解析开始,一直调整代码到执行阶段,还需要考虑DATE类型数据的存储。

注意:

实验记录

由于是从语法层面实现新的 Date type,所以需要从parser改起。这一部分基于lex+yacc,难度不大,只需要照着其他type(比如float)改起即可。

Parser 部分

value 的实现

这里修改的部分比较多,主要是添加了 AttrType::DATES 这个类,引发对应的set/get操作,以及to_string等方法。在存的时候,还是用了int去表达一个日期,这样节约空间,但是给转换类型带来了一些麻烦。

可以发现value.cpp里面已经有基础类型的转换,因此我主要通过额外实现关于date的类型get方法来进行转换,如果转换成int的过程不顺利,则返回 0 来告知上层调用者,以便于抛出FAILURE。因为FAILURE也在测试集之中。

int Value::get_date_int() const
{
  switch (attr_type_) {
    case AttrType::CHARS: {
      int year,month,day;
      int ret = sscanf(str_value_.c_str(),"%d-%d-%d",&year,&month,&day);
      int max_days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
      bool is_leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
      if(is_leap)max_days[1] = 29;
      bool is_valid = (year >= 1 && year <= 9999) && (month >= 1 && month <= 12) && (day >= 1 && day <= max_days[month-1]);
      if (ret != 3 || !is_valid) {
        LOG_WARN("failed to convert string to date. s=%s", str_value_.c_str());
        return 0;
      }
      return year * 10000 + month * 100 + day;
    } break;
    case AttrType::INTS: {
      return num_value_.int_value_;
    } break;
    case AttrType::FLOATS: {
      LOG_WARN("float type convert to date as yyyymmdd.");
      return (int)(num_value_.float_value_);
    } break;
    case AttrType::DATES: {
      return num_value_.int_value_;
    } break;
    case AttrType::BOOLEANS: {
      LOG_WARN("boolean type convert to date as 1 or 0.");
      return (int)(num_value_.bool_value_);
    } break;
    default: {
      LOG_WARN("unknown data type. type=%d", attr_type_);
      return 0;
    }
  }
  return 0;
}

std::string Value::get_date_string() const
{
  std::string date_str = this->to_string();
  int year,month,day;
  int ret = sscanf(date_str.c_str(),"%d-%d-%d",&year,&month,&day);
  if (ret != 3) {
    LOG_TRACE("failed to convert string to date. s=%s", date_str.c_str());
    return "0000-00-00";
  }
  return date_str;
}

stmt 部分

经过研究发现,要修改的是insert_stmt.cppfilter_stmt.cpp 。前者是在形如语句:

INSERT INTO date_table VALUES (1,'2020-01-21');

时,需要把识别到的字符串根据匹配的Field转换成DATES类型。

后者则是对于形如:

SELECT * FROM date_table WHERE u_date>'2020-1-20';

的语句把匹配到的日期字符串进行类型转换。

这两部分都在stmt的构造部分完成,从而不需要影响到下游的任务。

//insert_stmt.cpp
//...
// check fields type
  const int sys_field_num = table_meta.sys_field_num();
  for (int i = 0; i < value_num; i++) {
    const FieldMeta *field_meta = table_meta.field(i + sys_field_num);
    const AttrType   field_type = field_meta->type();
    const AttrType   value_type = values[i].attr_type();

    if (field_type != value_type) {  // TODO try to convert the value type to field type
      if(field_type == AttrType::DATES && value_type == AttrType::CHARS){//convert the string type to date type
          Value *value_i = const_cast<Value *>(&values[i]);//强制转非const的指针,很难受
          int date_int = values[i].get_date_int();
          if(!date_int){
            LOG_WARN("field type mismatch. table=%s, field=%s, field type=%d, value_type=%d, value=%s",
                table_name, field_meta->name(), field_type, value_type, value_i->get_string().c_str());
            return RC::SCHEMA_FIELD_TYPE_MISMATCH;
          }
          value_i->set_date(date_int);
          continue;
      }
      LOG_WARN("field type mismatch. table=%s, field=%s, field type=%d, value_type=%d",
          table_name, field_meta->name(), field_type, value_type);
      return RC::SCHEMA_FIELD_TYPE_MISMATCH;
    }
  }
//...
//filter_stmt.cpp
//...
  // 检查两个类型是否能够比较
  if(filter_unit->left().is_attr && !filter_unit->right().is_attr 
    && filter_unit->left().field.attr_type() == AttrType::DATES
    && filter_unit->right().value.attr_type() == AttrType::CHARS){
      FilterObj new_right = filter_unit->right();
      int date_int = filter_unit->right().value.get_date_int();
      if(!date_int){
        LOG_WARN("invalid date format: %s", filter_unit->right().value.get_string().c_str());
        return RC::SCHEMA_FIELD_TYPE_MISMATCH;
      }
      new_right.value.set_date(date_int);
      filter_unit->set_right(new_right);
    }
  else if(!filter_unit->left().is_attr &&  filter_unit->right().is_attr
    && filter_unit->left().value.attr_type() == AttrType::CHARS
    && filter_unit->right().field.attr_type() == AttrType::DATES){
      FilterObj new_left = filter_unit->left();
      int date_int = filter_unit->left().value.get_date_int();
      if(!date_int){
        LOG_WARN("invalid date format: %s", filter_unit->left().value.get_string().c_str());
        return RC::SCHEMA_FIELD_TYPE_MISMATCH;
      }
      new_left.value.set_date(date_int);
      filter_unit->set_left(new_left);
  }//和dates比较的chars类型直接转换成dates类型

//...

B+树部分

这个测试点居然还要求建索引……由于我把DATES这个类型用int进行存储,并且在补充value类型中data方法的时候返回的是DATES的int表达,这样很省事,B+树索引几乎可以直接用,但是要补充一下operator里对该类型的比较运算。

// /src/observer/storage/index/bplus_tree.h
//...
  int operator()(const char *v1, const char *v2) const
  {
    switch (attr_type_) {
      case AttrType::INTS: {
        return common::compare_int((void *)v1, (void *)v2);
      } break;
      case AttrType::FLOATS: {
        return common::compare_float((void *)v1, (void *)v2);
      }
      case AttrType::CHARS: {
        return common::compare_string((void *)v1, attr_length_, (void *)v2, attr_length_);
      }
      case AttrType::DATES: {
        return common::compare_int((void *)v1, (void *)v2);//here
      }
      default: {
        ASSERT(false, "unknown attr type. %d", attr_type_);
        return 0;
      }
    }
  }

测试点

位于 miniob/test/case/test/primary-date.test

可以用python脚本测试,也可以直接手搓检查。