引言
Bleve是Golang實現的一個全文檢索庫,類似Lucene之於Java。在這裡通過閱讀其程式碼,來學習如何使用及定製檢索功能。也是為了通過閱讀程式碼,學習在具體環境下Golang的一些使用方式。程式碼的路徑在github上https://github.com/blevesearch/bleve。
Index Mapping是bleve的一個功能特性,用來控制每個型別的文件,文件內的每個欄位,具體應該如何被分析、索引、儲存,這部分與Lucence的設計思路相同。
1 IndexMapping的結構
Bleve通過IndexMapping及各成員結構,來控制如何對每種型別的Document,Document內的NestDocument,Document內的各Field進行分析、索引和儲存。它本身是一個遞迴的結構,在DocumentMapping這一層可以進行任意深度的巢狀。
我們先整體看一下Index Mapping及其成員的從屬關係。下圖是我畫的結構簡圖,很多成員並未在圖上體現出來。本節的內容就是從左到右依次介紹每個結構型別的及其成員對應的物理意義。
1.1 IndexMapping
從上一篇文章Bleve程式碼閱讀(一)——新建索引我們知道,新建一個索引首先需要初始化一個IndexMapping結構。在示例程式碼中,這是通過函式bleve.NewIndexMapping()實現的,然後這個結構的指標被作為引數傳遞給bleve.New(index_path, index_mapping)。我們還知道,這個結構的內容被序列化後,儲存在index檔案裡了。因此,這個結構與索引是一一對應的。
下面我們先看一下IndexMapping的定義程式碼,這個結構在上一篇文章也給出了。
type IndexMappingImpl struct {
TypeMapping map[string]*DocumentMapping `json:"types,omitempty"`
DefaultMapping *DocumentMapping `json:"default_mapping"`
TypeField string `json:"type_field"`
DefaultType string `json:"default_type"`
DefaultAnalyzer string `json:"default_analyzer"`
DefaultDateTimeParser string `json:"default_datetime_parser"`
DefaultField string `json:"default_field"`
StoreDynamic bool `json:"store_dynamic"`
IndexDynamic bool `json:"index_dynamic"`
DocValuesDynamic bool `json:"docvalues_dynamic,omitempty"`
CustomAnalysis *customAnalysis `json:"analysis,omitempty"`
cache *registry.Cache
}
上圖最左邊的矩形,就是代表了IndexMappingImpl這個結構,下面都以IndexMapping這個名稱代替。TypeMapping也與圖中的同名矩形對應,它是IndexMapping的一個成員。我們這裡重點看一下這四個成員:
- TypeMapping:它是一個字串到DocumentMapping指標的map。它的key是字串,代表Document的型別。它的value是DocumentMapping的指標,用來定製該型別文件的索引方式。
- DefaultMapping:它是DocumentMapping的指標。當TypeMapping沒有配置某型別文件的DocumentMapping時,則該類文件使用該預設的DocumentMapping。
- DefaultAnalyzer:預設的根節點Analyzer。由於每個DocumentMapping、每個DocumentField都可以配置自己的Analyzer,當處理某個Field時,優先使用最個性化的設定。找不到個性化的才使用預設的,一級一級往上找,直到這裡。
- DefaultType:當一篇文件未提供分類時,就會使用DefaultType設定的型別名稱,預設是“_default”,也可以修改。需要注意的是,一個文件時DefaultType不等於它就要使用DefaultMapping,可以在TypeMapping中為DefaultType設定個性化的索引方式。
1.2 DocumentMapping
從本文開頭的圖中可以看出,DocumentMapping是最多出現的一個結構,在文件、嵌入文件、欄位這幾個概念層次上都要用到。任何一個文字結構,都應該對應一個DocumentMapping,文字結構包括整個文件、文件內的嵌入文件、文件或嵌入文件內的欄位。DocumentMapping的定義如下:
type DocumentMapping struct {
Enabled bool `json:"enabled"`
Dynamic bool `json:"dynamic"`
Properties map[string]*DocumentMapping `json:"properties,omitempty"`
Fields []*FieldMapping `json:"fields,omitempty"`
DefaultAnalyzer string `json:"default_analyzer,omitempty"`
// StructTagKey overrides "json" when looking for field names in struct tags
StructTagKey string `json:"struct_tag_key,omitempty"`
}
1.2.1 文件
圖中第二列最頂端的矩形,是一個對應原始文件的DocumentMapping。我們在這裡重點關注以下幾個成員:
- Enabled:如果為False,則整個文字結構不被index處理。
- Properties:一個map。key為文件的成員名,value是一個DocumentMapping的指標,該DocumentMapping對定義該成員的文字應該被如何處理。無論該成員是一個嵌入文件或是一個欄位,都需要一個DocumentMapping與之對應。
- Fields:一個FieldMapping指標的切片。作為DocumentMapping的成員,如果當前DocumentMapping對應於一個文件或嵌入文件,則該欄位為空。只有當前DocumentMapping對應一個欄位,該成員才有可能非空。每個FieldMapping用來控制該欄位應該被索引怎樣分析、儲存,一個欄位可以有多個分析、儲存方式。
- DefaultAnalyzer:當前文字結構的預設Analyzer。
1.2.2 嵌入文件
圖中第三列下面的矩形,是一個對應嵌入文件的DocumentMapping。它所有成員的意義與對應文件的DocumentMapping相同。
1.2.3 欄位
圖中第三列頂端的矩形,是一個對應欄位的DocumentMapping。它的成員的意義與對應文件的DocumentMapping略有不同,體現在以下兩方面:
- Properties:當它是一個對應欄位的DocumentMapping的成員時,為空。
- Fields:當它是一個對應欄位的DocumentMapping的成員時,才可以不為空。它是一個FieldMapping指標的切片,定義了該欄位應該被怎樣處理,可以有多個處理方式。
1.3 FieldMapping
FieldMapping描述了一個具體的欄位如何被放入索引。它一定包含於一個對應欄位DocumentMapping,而不能直接被對應文件的DocumentMapping包含。FieldMapping的定義如下:
type FieldMapping struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
// Analyzer specifies the name of the analyzer to use for this field. If
// Analyzer is empty, traverse the DocumentMapping tree toward the root and
// pick the first non-empty DefaultAnalyzer found. If there is none, use
// the IndexMapping.DefaultAnalyzer.
Analyzer string `json:"analyzer,omitempty"`
// Store indicates whether to store field values in the index. Stored
// values can be retrieved from search results using SearchRequest.Fields.
Store bool `json:"store,omitempty"`
Index bool `json:"index,omitempty"`
// IncludeTermVectors, if true, makes terms occurrences to be recorded for
// this field. It includes the term position within the terms sequence and
// the term offsets in the source document field. Term vectors are required
// to perform phrase queries or terms highlighting in source documents.
IncludeTermVectors bool `json:"include_term_vectors,omitempty"`
IncludeInAll bool `json:"include_in_all,omitempty"`
DateFormat string `json:"date_format,omitempty"`
// DocValues, if true makes the index uninverting possible for this field
// It is useful for faceting and sorting queries.
DocValues bool `json:"docvalues,omitempty"`
}
FieldMapping不會再包含任何上述結構了,它是整個IndexMapping的葉節點。它的成員大多是bool型,其他string型別的變數也大多是描述作用。
- Type:描述該欄位是作為那種型別,文字、數字、布林、日期、地理座標等。
- Store:該欄位是否被存入索引。
- Index:該欄位是否被索引。
- IncludeTermVectors:該欄位的occ是否被儲存,用於標紅、短語query。
- IncludeInAll:該欄位是否包含在all欄位裡,預設所有欄位都在all欄位,除非明確指定不包含。
- Analyzer:該欄位使用哪種Analyzer。
2 實踐落地
上一篇文章中建立新索引的程式碼,只包含了2個函式呼叫,程式碼重複如下。
indexMapping := bleve.NewIndexMapping()
index, err := bleve.New("example.bleve", mapping)
為了指定各欄位的索引方式,我們需要在初始化indexMapping後對其進行一些編輯修改。
blogMapping := bleve.NewDocumentMapping()
indexMapping.AddDocumentMapping("blog", blogMapping)
上面的程式碼,在indexMapping新增加了一個文件型別blog,併為其建立了一個DocumentMapping。此時的DocumentMapping是空的,是預設方式。
nameFieldMapping := bleve.NewTextFieldMapping()
nameFieldMapping.Analyzer = "en"
blogMapping.AddFieldMappingsAt("name", nameFieldMapping)
上面的程式碼為blog型別的文字的name欄位,添加了一個FieldMapping,並將其Analyzer指定為“en”。
注意:在1.2.1節中,我們提到了當DocumentMapping對應於文件或嵌入文件時,它不能包含FieldMapping。這裡呼叫方法
blogMapping.AddFieldMappingsAt("name", nameFieldMapping)
,相當於為blogMapping在Properties成員上添加了一個key為"name"的DocumentMapping,再將nameFieldMapping這個FieldMapping新增到這個中間DocumentMapping的Fields。
author := bleve.NewDocumentMapping()
authorNameFieldMapping := bleve.NewTextFieldMapping()
authorNameFieldMapping.Store = false
author.AddFieldMappingsAt("name", authorFieldNameMapping)
authorEmailFieldMapping := bleve.NewTextFieldMapping()
authorEmailFieldMapping.IncludeInAll = false
author.AddFieldMappingsAt("email", authorEmailFieldMapping)
blog.AddSubDocumentMapping("author", author)
上面的程式碼演示瞭如何在blog型別的DocumentMapping上新增一個嵌入文件的DocumentMapping。該嵌入文件在文件的key為author,包含name、email兩個成員。並且,name只索引不儲存,email不包含在_all欄位。