OceanBase大赛第四届-初赛记录-2.date
George_Plover · · 个人记录
date
- 难度:简单
- 题目总分:10
在现有功能上实现日期类型字段。
当前已经支持了int、char、float类型,在此基础上实现date类型的字段。date测试可能超过2038年2月,也可能小于1970年1月1号。注意处理非法的date输入,需要返回FAILURE。
这道题目需要从词法解析开始,一直调整代码到执行阶段,还需要考虑DATE类型数据的存储。
注意:
- 需要考虑date字段作为索引时的处理,以及如何比较大小;
- 这里没有限制日期的范围,需要处理溢出的情况。
- 需要考虑闰年。
实验记录
由于是从语法层面实现新的 Date type,所以需要从parser改起。这一部分基于lex+yacc,难度不大,只需要照着其他type(比如float)改起即可。
Parser 部分
-
src/observer/sql/parser/lex_sql.l只需添加对DATE识别的token。 -
src/observer/sql/parser/yacc_sql.y只需要加上type里的处理即可。
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.cpp 和 filter_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脚本测试,也可以直接手搓检查。