dataspace
/
space.go
480 строк · 11.4 Кб
1package dataspace
2
3import (
4"bufio"
5"bytes"
6"errors"
7"io"
8"os"
9"sync"
10
11"github.com/RoaringBitmap/roaring/v2/roaring64"
12"github.com/zeebo/xxh3"
13)
14
15////////////////////////////////////////////////////////////////////////////////
16
17const (
18Ascend uint8 = iota
19Descend
20Intersection
21Union
22)
23
24var (
25ErrKeyNotFound = errors.New("key not found")
26ErrKeyNil = errors.New("key is nil")
27ErrKeyMissing = errors.New("empty key")
28ErrValueNil = errors.New("value is nil")
29ErrValueMissing = errors.New("empty value")
30ErrIncorrectOrder = errors.New("incorrect order: must be Ascend or Descend")
31ErrIncorectOperation = errors.New("incorrect search operation: must be Intersection or Union")
32ErrFactorsNotSpecified = errors.New("search factors not specified")
33ErrFactorsNotCreated = errors.New("search factors not created")
34ErrFactorNotFound = errors.New("search factor not found")
35)
36
37// //////////////////////////////////////////////////////////////////////////////
38
39type Space struct {
40sync.RWMutex // мьютекс
41wal *os.File // файл
42writer *bufio.Writer // обработчик записи
43tree *node // сбалансированное дерево
44list *list // двусвязный список
45bitmaps map[string]*roaring64.Bitmap // битовые карты поиска
46}
47
48type bitmapStat struct {
49Name string `json:"name"`
50Length uint64 `json:"length"`
51SizeInBytes uint64 `json:"size-in-bytes"`
52}
53
54type Info struct {
55FileName string `json:"file-name"`
56FileSize int64 `json:"file-size"`
57Length uint64 `json:"length"`
58LastModified string `json:"last-modified"`
59TreeBranches int64 `json:"tree-branches"`
60Bitmaps []*bitmapStat `json:"bitmaps"`
61}
62
63////////////////////////////////////////////////////////////////////////////////
64
65// Конструктор нового пространства данных.
66func New(path string) (*Space, error) {
67w, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
68if err != nil {
69return nil, err
70}
71s := &Space{
72wal: w,
73writer: bufio.NewWriter(w),
74tree: &node{},
75list: newList(),
76bitmaps: make(map[string]*roaring64.Bitmap),
77}
78stat, err := s.wal.Stat()
79if err != nil {
80return nil, err
81}
82b := make([]byte, stat.Size())
83if _, err := w.Read(b); err != nil {
84return nil, err
85}
86s.load(b)
87return s, nil
88}
89
90// Метод создаёт новый фактор поиска.
91func (s *Space) CreateSearchFactor(name string) error {
92s.Lock()
93defer s.Unlock()
94if _, ok := s.bitmaps[name]; ok {
95return nil
96}
97s.bitmaps[name] = roaring64.NewBitmap()
98data := encode(&fraction{
99m: 2,
100s: true,
101k: []byte(name),
102v: nil,
103})
104if _, err := s.writer.Write(data); err != nil {
105return err
106}
107return s.writer.Flush()
108}
109
110// Метод удаляет фактор поиска.
111func (s *Space) DeleteSearchFactor(name string) error {
112s.Lock()
113defer s.Unlock()
114if b, ok := s.bitmaps[name]; ok {
115b.Clear()
116delete(s.bitmaps, name)
117}
118data := encode(&fraction{
119m: 2,
120s: false,
121k: []byte(name),
122v: nil,
123})
124if _, err := s.writer.Write(data); err != nil {
125return err
126}
127return s.writer.Flush()
128}
129
130// Метод записывает данные в пространство данных.
131func (s *Space) Set(k, v []byte) error {
132switch {
133case k == nil:
134return ErrKeyNil
135case len(k) == 0:
136return ErrKeyMissing
137case v == nil:
138return ErrValueNil
139case len(v) == 0:
140return ErrValueMissing
141}
142h := xxh3.Hash(k)
143s.Lock()
144defer s.Unlock()
145if node := s.tree.search(h); node != nil {
146if bytes.Equal(node.value, v) {
147return nil
148}
149s.tree = s.tree.add(h, k, v, node.element)
150} else {
151s.tree = s.tree.add(h, k, v, s.list.push(h))
152}
153data := encode(&fraction{
154m: 1,
155s: true,
156k: k,
157v: v,
158})
159if _, err := s.writer.Write(data); err != nil {
160return err
161}
162return s.writer.Flush()
163}
164
165// Метод получает данные из пространства данных.
166func (s *Space) Get(k []byte) ([]byte, error) {
167switch {
168case k == nil:
169return nil, ErrKeyNil
170case len(k) == 0:
171return nil, ErrKeyMissing
172}
173s.RLock()
174defer s.RUnlock()
175if node := s.tree.search(xxh3.Hash(k)); node != nil {
176return node.value, nil
177}
178return nil, ErrKeyNotFound
179}
180
181// Метод удаляет данные из пространства данных.
182func (s *Space) Delete(k []byte) error {
183switch {
184case k == nil:
185return ErrKeyNil
186case len(k) == 0:
187return ErrKeyMissing
188}
189h := xxh3.Hash(k)
190s.Lock()
191defer s.Unlock()
192if node := s.tree.search(h); node != nil {
193s.list.remove(node.element)
194} else {
195return ErrKeyNotFound
196}
197s.tree.remove(h)
198data := encode(&fraction{
199m: 1,
200s: false,
201k: k,
202v: []byte{},
203})
204if _, err := s.writer.Write(data); err != nil {
205return err
206}
207for nm, b := range s.bitmaps {
208if !b.Contains(h) {
209continue
210}
211b.Remove(h)
212data := encode(&fraction{
213m: 3,
214s: false,
215k: []byte(nm),
216v: k,
217})
218if _, err := s.writer.Write(data); err != nil {
219return err
220}
221}
222return s.writer.Flush()
223}
224
225/*
226Метод итерирует диапазон значений.
227Первым аргументом должен быть указан порядок итерации:
228
229Ascend или 0 - возрастание
230Descend или 1 - убывание
231
232Вторым аргументом должно быть количество (limit) итерируемых элементов.
233Если в качестве аргумента указан 0 - итерация будет по всем элементам.
234Если значение больше чем количество элементов - итерация так же будет
235по всем элементам.
236Третим аргументом должен быть сдвиг (offset). Например сдвиг 5 значит,
237что итерация начнется с 5 элемента.
238*/
239func (s *Space) Range(by uint8, limit, offset uint64) ([][]byte, error) {
240switch {
241case by != Ascend && by != Descend:
242return nil, ErrIncorrectOrder
243case s.list.len == 0:
244return nil, nil
245case limit == 0 || limit > s.list.len:
246limit = s.list.len
247}
248values := make([][]byte, 0, limit)
249s.RLock()
250defer s.RUnlock()
251switch by {
252case Ascend:
253for e := s.list.root.next; e != nil; e = e.nextElement() {
254if offset != 0 {
255offset--
256continue
257}
258if node := s.tree.search(e.value); node != nil {
259values = append(values, node.value)
260} else {
261return nil, ErrKeyNotFound
262}
263limit--
264if limit == 0 {
265break
266}
267}
268case Descend:
269for e := s.list.root.prev; e != nil; e = e.prevElement() {
270if offset != 0 {
271offset--
272continue
273}
274if node := s.tree.search(e.value); node != nil {
275values = append(values, node.value)
276} else {
277return nil, ErrKeyNotFound
278}
279limit--
280if limit == 0 {
281break
282}
283}
284}
285return values, nil
286}
287
288// Метод записывает ключ в факторы поиска.
289func (s *Space) SetToSearchFactors(k []byte, factors ...string) error {
290switch {
291case k == nil:
292return ErrKeyNil
293case len(k) == 0:
294return ErrKeyMissing
295case len(factors) == 0:
296return ErrFactorsNotSpecified
297case len(s.bitmaps) == 0:
298return ErrFactorsNotCreated
299}
300h := xxh3.Hash(k)
301s.Lock()
302defer s.Unlock()
303for _, f := range factors {
304if b, ok := s.bitmaps[f]; ok {
305if b.Contains(h) {
306continue
307}
308b.Add(h)
309data := encode(&fraction{
310m: 3,
311s: true,
312k: []byte(f),
313v: k,
314})
315if _, err := s.writer.Write(data); err != nil {
316return err
317}
318}
319}
320return s.writer.Flush()
321}
322
323// Метод удаляет ключ из факторов поиска.
324func (s *Space) DeleteFromSearchFactors(k []byte, factors ...string) error {
325switch {
326case k == nil:
327return ErrKeyNil
328case len(k) == 0:
329return ErrKeyMissing
330case len(factors) == 0:
331return ErrFactorsNotSpecified
332case len(s.bitmaps) == 0:
333return ErrFactorsNotCreated
334}
335h := xxh3.Hash(k)
336s.Lock()
337defer s.Unlock()
338for _, f := range factors {
339if b, ok := s.bitmaps[f]; ok {
340if !b.Contains(h) {
341continue
342}
343b.Remove(h)
344data := encode(&fraction{
345m: 3,
346s: false,
347k: []byte(f),
348v: k,
349})
350if _, err := s.writer.Write(data); err != nil {
351return err
352}
353}
354}
355return s.writer.Flush()
356}
357
358/*
359Метод осуществляет поиск значений по базе данных, которые соответствуют
360поисковым факторам, переданным в аргументах.
361*/
362func (s *Space) Search(by uint8, factors ...string) ([][]byte, error) {
363switch {
364case by != Intersection && by != Union:
365return nil, ErrIncorectOperation
366case len(factors) == 0:
367return nil, ErrFactorsNotSpecified
368case len(s.bitmaps) == 0:
369return nil, ErrFactorsNotCreated
370}
371v := make([][]byte, 0)
372s.RLock()
373defer s.RUnlock()
374if len(factors) == 1 {
375if b, ok := s.bitmaps[factors[0]]; ok {
376for _, k := range b.ToArray() {
377if node := s.tree.search(k); node != nil {
378v = append(v, node.value)
379} else {
380return nil, ErrKeyNotFound
381}
382}
383} else {
384return nil, ErrFactorNotFound
385}
386return v, nil
387}
388b := make([]*roaring64.Bitmap, 0, len(factors))
389for _, f := range factors {
390if bm, ok := s.bitmaps[f]; ok {
391b = append(b, bm)
392} else {
393return nil, ErrFactorNotFound
394}
395}
396switch by {
397case Intersection:
398for _, k := range roaring64.FastAnd(b...).ToArray() {
399if node := s.tree.search(k); node != nil {
400v = append(v, node.value)
401} else {
402return nil, ErrKeyNotFound
403}
404}
405case Union:
406for _, k := range roaring64.FastOr(b...).ToArray() {
407if node := s.tree.search(k); node != nil {
408v = append(v, node.value)
409} else {
410return nil, ErrKeyNotFound
411}
412}
413}
414return v, nil
415}
416
417// Метод возвращает информацию о пространстве данных.
418func (s *Space) Info() (*Info, error) {
419i, err := s.wal.Stat()
420if err != nil {
421return nil, err
422}
423bitmaps := make([]*bitmapStat, 0, len(s.bitmaps))
424for k, b := range s.bitmaps {
425bitmaps = append(bitmaps, &bitmapStat{
426Name: k,
427Length: b.GetCardinality(),
428SizeInBytes: b.GetSizeInBytes(),
429})
430}
431return &Info{
432FileName: i.Name(),
433FileSize: i.Size(),
434Length: s.list.len,
435LastModified: i.ModTime().Format("02.01.06 15:04"),
436TreeBranches: countBranches(s.tree),
437Bitmaps: bitmaps,
438}, nil
439}
440
441/*
442Метод создаёт резервный временный файл, затем копирует в него
443все данные. Далее очищает данные из основного wal-журнала и
444перезаписывает в порядке добавления с помощью списка.
445*/
446func (s *Space) Vacuum(path string) error {
447f, err := os.Create(path + ".temp")
448if err != nil {
449return err
450}
451s.Lock()
452defer s.Unlock()
453if _, err := s.wal.Seek(0, 0); err != nil {
454return err
455}
456if _, err := io.Copy(f, s.wal); err != nil {
457return err
458}
459if err := s.wal.Truncate(0); err != nil {
460return err
461}
462if _, err := s.wal.Seek(0, 0); err != nil {
463return err
464}
465if err := s.save(); err != nil {
466return err
467}
468if err := f.Close(); err != nil {
469return err
470}
471return os.Remove(path + ".temp")
472}
473
474// Метод закрывает пространство данных.
475func (s *Space) Close() error {
476if err := s.wal.Sync(); err != nil {
477return err
478}
479return s.wal.Close()
480}
481