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;
}
posted @ 2025-04-26 12:53  lucky_doog  阅读(39)  评论(0)    收藏  举报