250426 --- COMP9315 查找功能
COMP9315 25T1 A2
是实现 Multi-Attribute Linear Hashing,并且实现查找功能
我之前通过github把task1和task3做掉了,task2自己做出来一部分,实现了查询和投影功能
查询部分,实现了 ? 通配符,但是 % 没实现。
虽然已经交付,但我有点不放心。于是趁有时间,又做了一下task2的 % 通配符
查找功能
用法
./ query '2.1' from R where '101,2,?'
这个命令的意思是:从关系(表)R中查询满足条件 <101,2,?> 的记录。这里的 ? 是一个通配符,表示任何值都可以匹配。
并且按属性 <2,1> 的顺序打印出来(注意这里属性下标从1开始)
通配符含义
需要用到2种通配符
?: 代表整个属性都随意
%: 代表属性内的一部分字符可以随意
大意:
字面值(Literal value):必须在对应属性位置上精确匹配的特定值。例如,'abc'
匹配 'abc'
,'10'
匹配 '10'
。
单个问号(?
):在对应属性位置上匹配任何字面值。例如,'?'
匹配 'abc'
,'?'
匹配 '10'
。
包含 %
的模式字符串:这是一个包含一个或多个 %
的字符串,其中每个 %
匹配零个或多个字符。它支持灵活的基于模式的匹配。例如,'ab%'
匹配以 'ab'
开头的任何字面值,如 'abc'
、'ab123'
。
测试用例
实现方案
笔记
1. 关键函数
startSelection:解析查询条件,初始化选择过程。
getNextTuple:获取下一个符合条件的记录。
startProjection:初始化投影过程。
projectTuple:从记录中提取特定属性。
closeProjection:释放投影过程中使用的资源。
2. 查询选择和属性投影
查询的过程分为两个主要部分:
查询选择(select.c):根据条件选择符合条件的记录。
属性投影(project.c):从选中的记录中提取特定的属性。
然后通过 query.c
里的主函数实现调用
3. query.c里的main函数调用流程
int main(int argc, char **argv)
{
//一些变量的定义
Reln r; // handle on the open relation
Selection s; // handle on the selection
Projection p; // handle on the projection
Tuple t; // tuple pointer
char err[MAXERRMSG]; // buffer for error messages
int offset = 0; // adapt offset for -v
int verbose = 0; // show extra info on query progress
char *rname; // name of table/file
char *valstr; // a query string of values for selection
char *attrstr; // string of 1-based attribute indexes used for projection
// process command-line args
......
//这里省略一些命令行参数的解析和错误处理
//打开文件
if ((r = openRelation(rname,"r")) == NULL) {
sprintf(err, "Can't open relation: %s",rname);
fatal(err);
}
//调用startSelection,解析查询条件
if ((s = startSelection(r, valstr)) == NULL) {
sprintf(err, "Invalid selection: %s",valstr);
fatal(err);
}
//调用startProjection,解析投影条件
if ((p = startProjection(r, attrstr)) == NULL) {
sprintf(err, "Invalid projection: %s",attrstr);
fatal(err);
}
// execute the query (find matching tuples and project on specified attributes)
//核心循环,扫描所有项,若匹配上则调用projectTuple解析出投影结果,并打印
char tup[MAXTUPLEN];
while ((t = getNextTuple(s)) != NULL) {
projectTuple(p,t,tup);
printf("%s\n",tup);
}
// clean up 清理工作,可以不关心
closeProjection(p);
closeSelection(s);
closeRelation(r);
return 0;
}
笔记中用箭头表示了这些函数的调用关系:
首先调用 startSelection 解析查询条件。
然后调用 startProjection 初始化投影。
在选择过程中,调用 getNextTuple 获取下一个符合条件的记录。
对于每个选中的记录,调用 projectTuple 提取特定属性。
最后调用 closeProjection 释放资源。
具体代码
篇幅有限,我只贴核心部分代码
完整代码也可以到这里看:
https://gitee.com/wangqiyuejava63/comp9315-a2-25-t1
startSelection(select.c里的)
这个函数的主要功能是初始化,解析查询条件
为含有通配符 ?,% 的属性值,设置 unknown_bits
注意这一段代码是我额外加的,为的是识别到含有 % 通配符的查询条件
给他设置unknow_bits,后面查询的时候会用到这个unknown_bits
strstr(vals[choice_vector[i].att], "%")
Selection startSelection(Reln r, char *q)
{
.....
for(int i = 0; i < MAXBITS; ++i){
if(strcmp(vals[choice_vector[i].att], "?") == 0 || strstr(vals[choice_vector[i].att], "%")){
unknown_bits = setBit(unknown_bits, i);
}else{
int check = bitIsSet(hashKey, i);
if(check){
known_bits = setBit(known_bits, i);
}
}
}
.....
}
getNextTuple
getNextTuple的作用
-
确定当前页面:根据 Selection 对象中的信息(如 curpage 和 overflow_id),确定当前要处理的页面。
-
遍历页面中的元组:在当前页面中,逐个检查元组是否与查询条件匹配。这涉及到比较元组中的属性值与查询字符串中指定的值。
-
处理溢出页面:如果当前页面有溢出(overflow),即页面中的元组数量超过了页面容量,需要处理溢出页面中的元组。
-
匹配查询条件:对于每个元组,使用 tupleMatch 函数检查它是否满足查询条件。这涉及到比较元组中的属性值与查询字符串中的字面值、问号(?)或百分号(%)通配符。
-
更新状态:如果找到匹配的元组,更新 Selection 对象的状态,包括 curtup(当前元组的偏移量)、curtup_count(当前元组的计数)等。
-
返回匹配的元组:如果找到匹配的元组,返回该元组的数据。
-
处理页面结束:如果当前页面中没有找到匹配的元组,或者已经遍历完所有元组,更新 Selection 对象的状态,准备处理下一个页面。
-
递归或循环处理:如果没有找到匹配的元组,函数可能会递归调用自身(使用 goto start),或者通过循环遍历后续页面,直到找到匹配的元组或遍历完所有页面。
注意这里的unknown
if(bitIsSet(q -> known, depth(q -> rel)) || bitIsSet(q -> unknown, depth(q -> rel))){
PageID after_sp = setBit(q -> curpage, depth(q -> rel));
q -> curpage = after_sp;
goto start;
}
getNextTuple的定义
Tuple getNextTuple(Selection q)
{
.....省略一些定义和错误处理
//核心的扫描查询
// if there is tuple left in the current page
for(Offset cur_offset = q -> curtup; q -> curtup_count < t_total; cur_offset += (strlen(&pgdata[cur_offset])+1), ++q -> curtup_count){
//调用 tupleMatch,检查元组是否和查询条件匹配成功
if(tupleMatch(q -> rel, &pgdata[cur_offset], q -> qstring)){
//printf("Matched tuple: %s\n", &pgdata[cur_offset]); // 调试信息
++q -> curtup_count;
q -> curtup = cur_offset + strlen(&pgdata[cur_offset])+1;
return &pgdata[cur_offset];
}
}
// check if curr page has overflow and if the overflow page has overflow
.......以下省略对overflow页面的处理
// if the curpage is before sp check the depth + 1
if(q -> curpage < splitp(q -> rel)){
//if known [depth +1] == 1 or unknow [depth +1] == 1 we need to check the corr depth +1 page
if(bitIsSet(q -> known, depth(q -> rel)) || bitIsSet(q -> unknown, depth(q -> rel))){
PageID after_sp = setBit(q -> curpage, depth(q -> rel));
q -> curpage = after_sp;
goto start;
}
}
}
tupleMatch(tuple.c里的)
作用
tupleMatch 函数在数据库查询过程中被 getNextTuple 函数调用。
它的作用是检查一个元组(即数据库中的一行数据)是否与查询条件匹配。
这个函数会逐个比较元组中的属性值与查询字符串中指定的值,考虑到字面值(Literal value)、单个问号(?)和包含百分号(%)的模式字符串等不同情况。
具体来说,tupleMatch 函数会:
精确匹配字面值,即在对应属性位置上必须完全相同的值。
匹配单个问号(?),它表示在对应属性位置上可以是任何字面值。
匹配包含一个或多个百分号(%)的模式字符串,其中每个 % 可以匹配零个或多个字符,从而实现灵活的模式匹配。例如,模式 ab% 可以匹配任何以 ab 开头的字面值,如 abc 或 ab123。
通过这种方式,tupleMatch 函数确保只有满足查询条件的元组才会被选中并返回给用户。
fnmatch 通配符匹配
注意这里,使用了fnmatch函数
// 检查是否为通配符 '%'
char* pos = NULL;
while(NULL != (pos = strstr(v2[i], "%"))){
*pos = '*';
}
// 使用 fnmatch 检查模式匹配
if (fnmatch(v2[i], v1[i], 0)) {
match = FALSE;
break;
}else{
continue;
}
fnmatch 函数的作用
fnmatch 函数是 POSIX 标准的一部分,用于模式匹配,类似于 shell 中的文件名匹配。它的原型如下:
int fnmatch(const char *pattern, const char *string, int flags);
参数含义:
pattern 是模式字符串,可以包含 * 和 ? 作为通配符。
string 是要匹配的字符串。
flags 是一组标志,用于控制匹配的行为。常见的标志包括:
- FNM_PATHNAME:如果模式字符串包含 /,则只匹配路径名。也就是0
- FNM_NOESCAPE:禁用反斜杠 \ 的转义功能。也就是 2
- FNM_CASEFOLD:不区分大小写。也就是4
返回值:
- 0 匹配成功;
- FNM_NOMATCH,匹配失败;
- 其它非零值,匹配出错。
完整代码
Bool tupleMatch(Reln r, Tuple t1, Tuple t2)
{
Count na = nattrs(r);
char **v1 = malloc(na*sizeof(char *));
tupleVals(t1, v1);
char **v2 = malloc(na*sizeof(char *));
tupleVals(t2, v2);
Bool match = TRUE;
int i;
for (i = 0; i < na; i++) {
// assumes no real attribute values start with '?'
if (v1[i][0] == '?' || v2[i][0] == '?') continue;
if (strcmp(v1[i],v2[i]) == 0) continue;
// 检查是否为通配符 '%'
char* pos = NULL;
while(NULL != (pos = strstr(v2[i], "%"))){
*pos = '*';
}
// 使用 fnmatch 检查模式匹配
if (fnmatch(v2[i], v1[i], 0)) {
match = FALSE;
break;
}else{
continue;
}
match = FALSE;
}
freeVals(v1,na); freeVals(v2,na);
return match;
}
Bool tupleMatch(Reln r, Tuple t1, Tuple t2)
{
Count na = nattrs(r);
char **v1 = malloc(na*sizeof(char *));
tupleVals(t1, v1);
char **v2 = malloc(na*sizeof(char *));
tupleVals(t2, v2);
Bool match = TRUE;
int i;
for (i = 0; i < na; i++) {
// assumes no real attribute values start with '?'
if (v1[i][0] == '?' || v2[i][0] == '?') continue;
if (strcmp(v1[i],v2[i]) == 0) continue;
// 检查是否为通配符 '%'
char* pos = NULL;
while(NULL != (pos = strstr(v2[i], "%"))){
*pos = '*';
}
while(NULL != (pos = strstr(v1[i], "%"))){
*pos = '*';
}
// 使用 fnmatch 检查模式匹配
if (fnmatch(v2[i], v1[i], 0)) {
match = FALSE;
break;
}else{
continue;
}
match = FALSE;
}
freeVals(v1,na); freeVals(v2,na);
return match;
}