xapian简介<六>

2022-04-27 00:00:00 查询 数据 的是 分支 在这里

Xapian之数据读
Xapian数据查找的基本的步骤的伪代码,我以此为例来简单的说下xapian的数据查找。

Xapian::Database db(STR_PATH);

Xapian::Enquire enquire(db);

enquire.set_query(Xapian::Query::MatchAll);

Xapian::MSet matches = enquire.get_mset(0, 10);



DataBase的初始化,上面提过的部分我不重提,这里主要说下,xapian通过判断你提交的路径下的version文件也就是iambrass这种文件是否存在来判断当前数据库使用的后端是哪一种类型的。



3.1 Query的组装
Query是查询的时候主要的类,跟这个提供的条件查询出的结果集,根据这个查询出来的是完整的全部的查询的结果,之后再根据其他类提供的约束再会对这个结果集进行第二次过滤再送出你真正需要的结果。那么先来看下这个查询核心类的结构。





在这里主要的核心方法在于Query提供的9个重载方法,和enum op。Op提交了查询的条件,我就不一一列举了搞得我跟凑字数的一样,自己看文档。Query主要是运用OP配合query提供的重载构造函数完成一条查询语句,比如这句

Xapian::Query(Xapian::Query::OP_VALUE_RANGE, 1, "16", "29");

查询的op是范围查询,目前是slot为1的value,范围是16到29。这里的op很重要,决定后返回的PostList的子类便是根据这个来判断的。Xapian支持用逻辑与或之类的条件来链接两个query产生新的query。

3.2 Enquire类
这个类的成员变量就是所有查询的条件。如果你想要加入自己的条件就在这里加入。我的聚合函数的宏定义也是在这里。Enquire主要的的成员变量:

Query query; 查询条件

KeyMaker * sorter;对后的结果集进行排序

vector<MatchSpy *> spies; 对后的结果集进行分组

根据这个类的所有约束条件查询出了后的结果,Enquire会返回一个MSet的类。这个类保存了结果并且提供了迭代器访问的形式。



步,调用Enquire的get_mset方法这里开启查询,首先他会把所有的查询条件即自身的成员变量,构造MultiMatch的类时候传入部分数据,比如这里传入的是否存在的bool值就和我们的storage本地查询没有任何关系。接着会在MultiMatch的get_mset方法传入剩下的数据,在这里完成了全部的数据传入,他的工作就交给了MultiMatch。并且通过传入的Mset通过引用的方式拿回查询的结果集。



第二步,在MultiMatch的构造函数主要判断是远程数据库即多数据库查询,还是本地查询,然后根据这个来为std::vector<Xapian::Internal::RefCntPtr<SubMatch> >添加成员,在storage用的是本地查询顾这步大大的简化,也就是说接下来的db.size是1,std::vector<Xapian::Internal::RefCntPtr<SubMatch> >的size也是1。同时SubMatch实例化的是LocalSubMatch。



第三步,在MultiMatch的一堆准备工作之后进入了整个查询步骤核心的方法~!

leaves[i]->get_postlist_and_term_info,这里如前文所提,在storage这个leaves的size就是1。同时是LocalSubMatch的实现。追溯这个方法一直到尽头,看到了我前文所提的OP派上了大用处!!!这段代码老夫就是拼着凑字数的危险也要贴出来!

switch (query->op) {undefined

case Xapian::Query::Internal::OP_LEAF:

case Xapian::Query::Internal::OP_EXTERNAL_SOURCE:

case Xapian::Query::OP_AND:

case Xapian::Query::OP_FILTER:

case Xapian::Query::OP_NEAR:

case Xapian::Query::OP_PHRASE:

case Xapian::Query::OP_OR:

case Xapian::Query::OP_XOR:

case Xapian::Query::OP_ELITE_SET:

case Xapian::Query::OP_SYNONYM:

case Xapian::Query::OP_AND_NOT:

case Xapian::Query::OP_AND_MAYBE:

case Xapian::Query::OP_VALUE_RANGE:

case Xapian::Query::OP_VALUE_GE:

case Xapian::Query::OP_VALUE_LE:

case Xapian::Query::OP_SCALE_WEIGHT:

default:

}





以上的分支对应如下的类图:

简单的说下,这个继承树就是包含了所有查询结果返回的Postlist,xapian查询的精华和核心所在就是这里了,这里采用我开篇所说的迭代器设计模式,在MultiMatch::get_mset方法中,之后的排序也好,分组也好之类的操作都是根据这里返回的PostList得结果计算的,就如迭代器模式那样,我不关心你每个子类的的数据是如何产生的,我只是使用相同的接口也好了,王总的默认排序也是改了PostList的接口。接下来我具体的讲下每个分支的来历。



1.OP_LEAF这个分支是在Query查询term的时候命中。这个分支对应类图的LeafPostList分支,这个首先会根据他的后端决定是哪个PostList,然后根据查询是否是Match::all也就是相当于select *决定是哪个AllDocsPostList。这个LeafPostList分支不同于其他的分支原因在于他和其他分支在内部的得到有效的did内存块不同。这个分支是直接以term即查询时直接给出的term为key去PostListTable中的C[j].p的内存(C[j].p数据写时有论述,这里不啰嗦)。根据Ti->Dj的信息检索原理PostListTable这个的这块内存就保存了所有document的id,另外xapian在这里还保存了wdf,wdf是计算文件相关度要用的值。顺便说一句啊,王总的默认排序保存的值也在这里~,再说下其他的分支的key是文档id和slot组成的key,而且次查出来的是value元数据即valueStats的数据。

Storage中所用的数据写都是以value的方式,所以这个分支根本不会进入。

2.OP_AND,OP_FILTER,OP_NEAR,OP_PHRASE,OP_OR,OP_XOR,OP_ELITE_SET这里面递归的产生了PostList,举个例子加入Query本身是由两个Query用and连接的,然后这两个Query其中的一个又是另外两个Query用and组成的时候,你现在要做的就是把这个后包装好的Query,一层层解开,所以用了递归,然后解析到的一层,query就是由这些基本的查询构成的,然后这些基本的可能是一个范围查询,自然这个基本的query走的自然是其他的分支,然后这些所有的基本的query得到的PostList被组装在一起。PostList在and的时候本身内部就是维护了两个PostList,并且这个可以按照这个逻辑递归下去。

3.OP_AND_NOT,OP_AND_MAYBE,OP_VALUE_RANGE,OP_VALUE_GE,OP_VALUE_LE这几个都是根本其本身的字面意思来的,具体的含义看xapian的文档。直接返回相应的PostList和2所提的区别是这个单一的PostList,而2处返回的是复合的PostList。

在上面我们完成了PostList的获取,也就是这步做完我们得到了,接下来就是这些数据的处理,在处理之前提一点,term和value从query到得到的PostList到要从PostListTable中拿出数据的方式都不一致,term的时候可以直接以term为key就已经获得了文档号。而value命中的所有的文档号,其内部都维护了ValueList,这个数据是通过valueList拿到的,而valueList也是迭代器模式定义的一个外部接口,其内部又有如下子类:







接下来说下取出的PostList接下来的故事,也就是排序和分组。

在这里程序会进入一个while(true)的大循环,首先咱们往get_mset传入数据的时候会有一个limit的条件,在这里limit就是这个大循环会break的条件之一。排序后的算法执行虽然是在while大循环外面做的,但是他本身的数据items是在大循环里面拿到的,如果limit条件生效,break产生也就是items的长度也被limit了,同理虽然分组MatchSpy也是在大循环里面做的,但是也受到break的影响。

排序的话,首先这里用到了std::sort方法,这里使用了函数对象作为算法传入。MSetCmp这个类就是算法类,这里面有一个数组里面提供了16个模板函数。在MSetCmp类的构造函数中通过是否根据文件相关度,是否根据slot,是否降序或者升序来判断使用哪个算法。

排序首先根据文档did然后拿到了文档的数据,然后再根据需要排序的slot,取得字符串并把它拼到前面的slot中,举个例子比如:

0 1

h j

1.取得根据slot为0从document处取得了字符h,其中为true的话就会用255-h并在末尾添加\xff\xff标记遇0字符补0,为false的话在末尾什么都不做遇0补\xff

2.根slot为1从document处取得了字符j,然后上面说的true和false得到新的字符并把它和上面加工后的h字符组合。

3.根据以上的规则后得到的字符用于std::sort排序。



Xapian中的分组只实现了统计分组的个数,通过MatchSpy这个类完成分组操作,在这里我重新写了一个类实现了max,min,sum,count等聚合函数。



在经过以上的一系列操作之后,得到了items等数据根据这些数据构造MSet类,后在上层返回这个类,然后根据这个类完成对读数据的获取。本身数据在一开始读出来的时候拿到的仅仅是文档号和wdf和一些元数据,以value为例,要想获得真正的数据,还需要以获得文档号和你想获得value对应的slot号,组合起来一起为key,查询数据库维护的内存。



至此,我们的xapian之旅也告于段落了,还有很多值得深究的地方。
————————————————
版权声明:本文为CSDN博主「RobotZKB」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZHOUKUANBIN/article/details/51578644

相关文章