-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstore.go
More file actions
146 lines (125 loc) · 3.87 KB
/
Copy pathstore.go
File metadata and controls
146 lines (125 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package gomem
import (
"fmt"
"os"
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/mapping"
"github.com/blevesearch/bleve/v2/search/query"
)
// Store wraps a Bleve index for persistent memory storage.
type Store struct {
index bleve.Index
}
// NewStore opens an existing Bleve index at path or creates a new one.
func NewStore(path string) (*Store, error) {
// Ensure data directory is only accessible by the owner
os.MkdirAll(path, 0700)
index, err := bleve.Open(path)
if err != nil {
// If the index doesn't exist (path missing) or the directory is empty
// (meta missing), create a new index.
if err == bleve.ErrorIndexPathDoesNotExist || err == bleve.ErrorIndexMetaMissing {
m := buildIndexMapping()
index, err = bleve.New(path, m)
}
if err != nil {
return nil, fmt.Errorf("open index: %w", err)
}
}
return &Store{index: index}, nil
}
// Remember stores a text entry in the index under the given ID.
func (s *Store) Remember(id, text string) error {
doc := MemoryDoc{
ID: id,
Text: text,
}
if err := s.index.Index(id, doc); err != nil {
return fmt.Errorf("index document: %w", err)
}
return nil
}
// Search performs a full-text query against the index and returns matching hits.
func (s *Store) Search(q string, limit int) ([]SearchHit, uint64, error) {
if limit <= 0 || limit > 100 {
limit = 10
}
var qry query.Query
if q == "*" {
// MatchAllQuery for listing all documents
qry = query.NewMatchAllQuery()
} else {
// MatchQuery (simpler, faster than QueryStringQuery).
// The query text is analyzed with the field's analyzer for token matching.
mq := query.NewMatchQuery(q)
mq.SetField("text")
qry = mq
}
searchRequest := bleve.NewSearchRequestOptions(qry, limit, 0, false)
searchRequest.Fields = []string{"text"}
result, err := s.index.Search(searchRequest)
if err != nil {
return nil, 0, fmt.Errorf("search: %w", err)
}
hits := make([]SearchHit, 0, len(result.Hits))
for _, hit := range result.Hits {
hits = append(hits, SearchHit{
ID: hit.ID,
Score: hit.Score,
Text: fieldString(hit.Fields, "text"),
})
}
return hits, result.Total, nil
}
// Delete removes a document from the index by ID.
// Returns an error if the document doesn't exist.
func (s *Store) Delete(id string) error {
// Search for the document first to verify it exists.
// Bleve's Delete doesn't error on missing docs.
q := query.NewDocIDQuery([]string{id})
search := bleve.NewSearchRequestOptions(q, 1, 0, false)
result, err := s.index.Search(search)
if err != nil {
return fmt.Errorf("delete: %w", err)
}
if result.Total == 0 {
return fmt.Errorf("document %q not found", id)
}
if err := s.index.Delete(id); err != nil {
return fmt.Errorf("delete document: %w", err)
}
return nil
}
// Close closes the underlying Bleve index, flushing all pending writes.
func (s *Store) Close() error {
return s.index.Close()
}
// DocCount returns the total number of indexed documents.
func (s *Store) DocCount() (uint64, error) {
return s.index.DocCount()
}
// buildIndexMapping creates a Bleve index mapping for MemoryDoc.
func buildIndexMapping() mapping.IndexMapping {
m := bleve.NewIndexMapping()
docMapping := bleve.NewDocumentMapping()
// ID field — keyword (not analyzed)
idFieldMapping := bleve.NewTextFieldMapping()
idFieldMapping.Analyzer = "keyword"
docMapping.AddFieldMappingsAt("id", idFieldMapping)
// Text field — analyzed for full-text search (no stemming = faster)
textFieldMapping := bleve.NewTextFieldMapping()
textFieldMapping.Analyzer = "standard"
docMapping.AddFieldMappingsAt("text", textFieldMapping)
m.AddDocumentMapping("memory", docMapping)
m.DefaultAnalyzer = "standard"
return m
}
// fieldString safely extracts a string from a document field map.
func fieldString(fields map[string]interface{}, key string) string {
v, ok := fields[key]
if !ok {
return ""
}
s, _ := v.(string)
return s
}