github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/system/search/search.go (about) 1 // Package search is a wrapper around the blevesearch/bleve search indexing and 2 // query package, and provides interfaces to extend Ponzu items with rich, full-text 3 // search capability. 4 package search 5 6 import ( 7 "encoding/json" 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "github.com/rpdict/ponzu/system/item" 15 16 "github.com/blevesearch/bleve" 17 "github.com/blevesearch/bleve/mapping" 18 ) 19 20 var ( 21 // Search tracks all search indices to use throughout system 22 Search map[string]bleve.Index 23 24 // ErrNoIndex is for failed checks for an index in Search map 25 ErrNoIndex = errors.New("No search index found for type provided") 26 ) 27 28 // Searchable ... 29 type Searchable interface { 30 SearchMapping() (*mapping.IndexMappingImpl, error) 31 IndexContent() bool 32 } 33 34 func init() { 35 Search = make(map[string]bleve.Index) 36 } 37 38 // MapIndex creates the mapping for a type and tracks the index to be used within 39 // the system for adding/deleting/checking data 40 func MapIndex(typeName string) error { 41 // type assert for Searchable, get configuration (which can be overridden) 42 // by Ponzu user if defines own SearchMapping() 43 it, ok := item.Types[typeName] 44 if !ok { 45 return fmt.Errorf("[search] MapIndex Error: Failed to MapIndex for %s, type doesn't exist", typeName) 46 } 47 s, ok := it().(Searchable) 48 if !ok { 49 return fmt.Errorf("[search] MapIndex Error: Item type %s doesn't implement search.Searchable", typeName) 50 } 51 52 // skip setting or using index for types that shouldn't be indexed 53 if !s.IndexContent() { 54 return nil 55 } 56 57 mapping, err := s.SearchMapping() 58 if err != nil { 59 return err 60 } 61 62 idxName := typeName + ".index" 63 var idx bleve.Index 64 65 // check if index exists, use it or create new one 66 pwd, err := os.Getwd() 67 if err != nil { 68 return err 69 } 70 71 searchPath := filepath.Join(pwd, "search") 72 73 err = os.MkdirAll(searchPath, os.ModeDir|os.ModePerm) 74 if err != nil { 75 return err 76 } 77 78 idxPath := filepath.Join(searchPath, idxName) 79 if _, err = os.Stat(idxPath); os.IsNotExist(err) { 80 idx, err = bleve.New(idxPath, mapping) 81 if err != nil { 82 return err 83 } 84 idx.SetName(idxName) 85 } else { 86 idx, err = bleve.Open(idxPath) 87 if err != nil { 88 return err 89 } 90 } 91 92 // add the type name to the index and track the index 93 Search[typeName] = idx 94 95 return nil 96 } 97 98 // UpdateIndex sets data into a content type's search index at the given 99 // identifier 100 func UpdateIndex(id string, data interface{}) error { 101 // check if there is a search index to work with 102 target := strings.Split(id, ":") 103 ns := target[0] 104 105 idx, ok := Search[ns] 106 if ok { 107 // unmarshal json to struct, error if not registered 108 it, ok := item.Types[ns] 109 if !ok { 110 return fmt.Errorf("[search] UpdateIndex Error: type '%s' doesn't exist", ns) 111 } 112 113 p := it() 114 err := json.Unmarshal(data.([]byte), &p) 115 if err != nil { 116 return err 117 } 118 119 // add data to search index 120 return idx.Index(id, p) 121 } 122 123 return nil 124 } 125 126 // DeleteIndex removes data from a content type's search index at the 127 // given identifier 128 func DeleteIndex(id string) error { 129 // check if there is a search index to work with 130 target := strings.Split(id, ":") 131 ns := target[0] 132 133 idx, ok := Search[ns] 134 if ok { 135 // add data to search index 136 return idx.Delete(id) 137 } 138 139 return nil 140 } 141 142 // TypeQuery conducts a search and returns a set of Ponzu "targets", Type:ID pairs, 143 // and an error. If there is no search index for the typeName (Type) provided, 144 // db.ErrNoIndex will be returned as the error 145 func TypeQuery(typeName, query string, count, offset int) ([]string, error) { 146 idx, ok := Search[typeName] 147 if !ok { 148 return nil, ErrNoIndex 149 } 150 151 q := bleve.NewQueryStringQuery(query) 152 req := bleve.NewSearchRequestOptions(q, count, offset, false) 153 res, err := idx.Search(req) 154 if err != nil { 155 return nil, err 156 } 157 158 var results []string 159 for _, hit := range res.Hits { 160 results = append(results, hit.ID) 161 } 162 163 return results, nil 164 }