本教程主要介绍WCDB-iOS/macOS中全文搜索的用法。

阅读本教程前,建议先阅读 iOS/macOS使用教程ORM使用教程基础类、CRUD与Transaction

介绍

全文搜索 ( Full-Text Serach ),是SQLite提供的功能之一,支持更快速、更便捷地搜索数据库内的信息。更多信息可参考SQLite的官方文档

WCDB内建FTS的支持,使用更简便,搜索更智能,分词更适合中文、日文等非空格分割的语言搜索。

基本用法

ORM

  1. //WCTSampleFTSData.h
  2. @interface WCTSampleFTSData : NSObject
  3.  
  4. @property(nonatomic, retain) NSString *name;
  5. @property(nonatomic, retain) NSString *content;
  6.  
  7. @end
  8.  
  9. //WCTSampleFTSData+WCTTableCoding.h
  10. @interface WCTSampleFTSData (WCTTableCoding) <WCTTableCoding>
  11.  
  12. WCDB_PROPERTY(name)
  13. WCDB_PROPERTY(content)
  14.  
  15. @end
  16.  
  17. //WCTSampleFTSData.mm
  18. @implementation WCTSampleFTSData
  19.  
  20. WCDB_IMPLEMENTATION(WCTSampleFTSData)
  21. WCDB_SYNTHESIZE(WCTSampleFTSData, name)
  22. WCDB_SYNTHESIZE(WCTSampleFTSData, content)
  23.  
  24. WCDB_VIRTUAL_TABLE_MODULE(WCTSampleFTSData, WCTModuleNameFTS3)
  25. WCDB_VIRTUAL_TABLE_TOKENIZE(WCTSampleFTSData, WCTTokenizerNameWCDB)
  26.  
  27. @end

FTS的ORM与普通表的ORM很类似。

将一个已有的ObjC类进行FTS的ORM绑定的过程如下:

  • 使用WCDB_PROPERTY宏在头文件声明需要绑定到数据库表的字段。
  • 使用WCDB_IMPLEMENTATIO宏在类文件定义绑定到数据库表的类。
  • 使用WCDB_SYNTHESIZE宏在类文件定义需要绑定到数据库表的字段。
  • 使用WCDB_VIRTUAL_TABLE_MODULE定义使用的虚拟表的模块。
  • 使用WCDB_VIRTUAL_TABLE_TOKENIZE定义使用搜索使用的分词器。
    如无特殊需求,模块和分词器可使用默认的WCTModuleNameFTS3WCTTokenizerNameWCDB即可。后面会进一步讨论模块和分词器。

ORM的限制

SQLite的FTS是使用虚拟表实现的,因此其与虚拟表有同样的限制

  • 不支持创建触发器
  • 不支持、也不需要创建索引
  • 不支持通过ALTER TABLE为虚拟表添加新的字段

建表

  1. WCTDatabase *databaseFTS = [[WCTDatabase alloc] initWithPath:pathFTS];
  2. [databaseFTS setTokenizer:WCTTokenizerNameWCDB];
  3. [databaseFTS createVirtualTableOfName:tableNameFTS withClass:WCTSampleFTSData.class];

建FTS表与建普通表也很类似。对于已经定义FTS-ORM的类WCTSampleFTSData和已创建的数据databaseFTS

  • 通过- setTokenizer:接口对当前数据库注册分词器,这里需与ORM定义的分词器一致。开发者也可以通过- setTokenizers:注册多个分词器。
  • 通过- createVirtualTableOfName:withClass:接口创建FTS表。

建立FTS索引

  1. WCTSampleFTSData *object = [[WCTSampleFTSData alloc] init];
  2. object.name = @"English";
  3. object.content = @"This is sample content";
  4. object.isAutoIncrement = YES;
  5. [databaseFTS insertObject:object into:tableNameFTS];

开发者只需要将数据插入到FTS表中,即可建立FTS索引。插入方式与普通表没有区别。

FTS搜索

  1. NSArray<WCTSampleFTSData *> *ftsDatas = [databaseFTS getObjectsOfClass:WCTSampleFTSData.class fromTable:tableNameFTS where:WCTSampleFTSData.name.match("Eng*")];
  2. for (WCTSampleFTSData *ftsData in ftsDatas) {
  3. NSLog(@"Match name:%@ content:%@", ftsData.name, ftsData.content);
  4. }

FTS搜索与普通表的方式类似,只是在where语句中,需要用match来搜索。

全表搜索

FTS表不仅可以针对某个字段进行搜索,也可以使用内置的隐藏字段进行全表搜索。隐藏字段与表名一致,可以通过className.PropertyNamed(tableName)获取。

  1. NSArray<WCTSampleFTSData *> *ftsDatas = [databaseFTS getObjectsOfClass:WCTSampleFTSData.class fromTable:tableNameFTS where:WCTSampleFTSData.PropertyNamed(tableNameFTS).match("Eng*")];
  2. for (WCTSampleFTSData *ftsData in ftsDatas) {
  3. NSLog(@"Match name:%@ content:%@", ftsData.name, ftsData.content);
  4. }

FTS搜索的示例代码请参考:sample_fts_main

FTS模块

SQLite支持FTS3FTS5两种搜索模块,可通过WCDB_VIRTUAL_TABLE_MODULE(WCTSampleFTSData, WCTModuleNameFTS3)WCDB_VIRTUAL_TABLE_MODULE(WCTSampleFTSData, @"FTS5")进行定义。WCDB默认使用FTS3

关于 FTS3FTS5 的差异,可参考SQLite的官方文档 SQLite FTS3 and FTS4 ExtensionsSQLite FTS5 Extension

FTS分词器

若只用一句话概括,FTS搜索的原理是将文字信息通过分词器切断为字词组成的数组,并以此建立搜索树。因此,分词器是搜索效率、准确度的关键。

SQLite内置了simpleporterunicode61等多个分词器,但其适合场景有限,这里不做深入讨论,开发者可自行搜索。

这里重点讨论WCDB内置的分词器, WCTTokenizerNameAppleWCTTokenizerNameWCDB

Apple分词器

WCTTokenizerNameApple是WCDB内置的一个分词器,它使用 CoreFoundation 内置的 CFStringTokenizer 对语句进行分割。

CFStringTokenizer 会过滤符号,并根据语言、语义对语句进行分割。

在iOS的任一输入框的文字中长按,并在弹出菜单中点击 "选择",iOS会智能地根据当前游标选择附近的一个词组。这个就是 CFStringTokenizer 的分词效果。

以一个词组 "苹果树" 为例,CFStringTokenizer 根据语义,会将其分割为 "苹果" 和 "树" 两个词组。

因此,使用 Apple分词器 的 FTS 表内会有该两个字段。开发者只需使用 className.PropertyNamed(tableName).match("苹果")即可搜索到对应的数据。

Apple分词器的局限性

上面提到,FTS搜索是将字段切断后组成B树,也就是说,搜索是根据切割后词组的首字符逐个匹配过去的。

因此,以"苹果树"的例子来说,"果树" 这一关键词因为无法首字匹配 "苹果" 和 "树",它无法被搜索到。而在中文中,这一例子里有"苹果"、"果树"、"树"、"苹果树"等多个有意义的词组。因此,虽然Apple分词器可以智能地基于语义进行分词,但其并不符合部分场景和部分语言。

同时,FTS通常用于app内搜索,其使用场景与搜索引擎不同。搜索引擎只要求将最符合条件的前几页数据搜索出来,因此搜索"果树"但结果中没有"苹果树"是符合场景的。而FTS要求的触达率要求是100%,即只要app内有这个数据,就应该被搜索出来。

WCDB分词器

WCTTokenizerNameWCDB是WCDB内置分词器,也是我们优先推荐的分词器。

符号

WCDB分词器会过滤所有Unicode编码中符号、空格、制表符、不可见和非法字符等。即Unicode编码字集中的 [ Cc, Cf, Z, U000A ~ U000D, U0085, M, P, S ],详情可参考 Unicode Character Categories

英文

WCDB分词器会将英文按照单词进行词形还原。

以句子"WCDB is a cross-platform database framework developed by WeChat."为例,由于英文存在多种变形和时态,传统的分词器无法通过"are"搜索到"is",无法通过"develop"搜索到"developed"。而词形还原会将单词还原为一般形态,从而使得WCDB分词器有更强的英文搜索能力。

中文

包括中文、日文在内的多种语言,都是通过语义,而不是空格或符号进行分割。WCDB分词器会将他们按照单双字符的逻辑进行分词。

同样以"苹果树"的逻辑为例,WCDB分词器会按照顺序,将其分割为:"苹果"、"苹"、"果树"、"果"、"树"。因此其可以确保各种组合都能正确匹配到。

自定义分词器

若开发者对已有的分词器不满意,也可以自定义新的分词器。

WCDB提供了文件模版用来定义分词器,开发者可参考 Apple分词器WCDB分词器 实现。

FTS全文搜索使用教程 - 图1

FTS全文搜索使用教程 - 图2