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  }