finish index feature

This commit is contained in:
2026-04-07 11:49:42 +06:00
parent 8ba956d8c5
commit 15db6e81db
37 changed files with 1047 additions and 170 deletions

View File

@@ -6,26 +6,37 @@ import (
"sync"
)
// IndexPosition — позиция записи в индексе (партиция + номер строки).
// Partition "default" — для обычных коллекций без партиционирования.
type IndexPosition struct {
Partition string // имя партиции ("default" для обычных коллекций)
LineIndex int
}
// DefaultPartition — значение партиции для непартиционированных коллекций.
const DefaultPartition = "default"
// IndexStore — интерфейс хранилища индексов.
// Позволяет подключать разные реализации: в памяти, memcached и др.
// Индекс привязан к логической коллекции; для партиционированных хранит (partition, lineIndex).
type IndexStore interface {
// Lookup ищет позиции записей по полю и значению.
// value — строковое представление (см. valueToIndexKey).
// Возвращает срез индексов строк (0-based) или nil, nil,
// если индекс не используется или записей нет.
Lookup(collection, field, value string) ([]int, error)
// Возвращает (partition, lineIndex) — для непартиционированных partition = DefaultPartition.
Lookup(collection, field, value string) ([]IndexPosition, error)
// IndexRecord добавляет одну запись в индекс по номеру строки (для точечных Update).
IndexRecord(collection string, fields []string, record map[string]any, lineIndex int)
// IndexRecord добавляет одну запись в индекс.
// partition — имя партиции (DefaultPartition для обычных коллекций).
IndexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int)
// UnindexRecord удаляет одну запись из индекса по номеру строки.
UnindexRecord(collection string, fields []string, record map[string]any, lineIndex int)
// UnindexRecord удаляет одну запись из индекса.
UnindexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int)
// Rebuild полностью перестраивает индекс коллекции.
// records — полный список записей, каждая позиция в срезе соответствует номеру строки в файле.
Rebuild(collection string, fields []string, records []any) error
// Rebuild перестраивает вклад в индекс для одной партиции (или всей коллекции, если одна партиция).
// partition — имя партиции; records — записи, позиция в срезе = lineIndex.
Rebuild(collection, partition string, fields []string, records []any) error
// Clear очищает индекс коллекции.
// Clear очищает индекс коллекции (все партиции).
Clear(collection string) error
}
@@ -65,14 +76,14 @@ func getFieldValue(record map[string]any, field string) string {
// InMemoryIndexStore — реализация IndexStore в памяти (по умолчанию).
type InMemoryIndexStore struct {
mu sync.RWMutex
// index: collection:field -> value -> список индексов строк (0-based)
index map[string]map[string][]int
// index: collection:field -> value -> []IndexPosition
index map[string]map[string][]IndexPosition
}
// NewInMemoryIndexStore создаёт новый in-memory индекс.
func NewInMemoryIndexStore() *InMemoryIndexStore {
return &InMemoryIndexStore{
index: make(map[string]map[string][]int),
index: make(map[string]map[string][]IndexPosition),
}
}
@@ -82,27 +93,33 @@ func (s *InMemoryIndexStore) indexKey(collection, field string) string {
}
// IndexRecord добавляет запись в индекс.
func (s *InMemoryIndexStore) IndexRecord(collection string, fields []string, record map[string]any, lineIndex int) {
func (s *InMemoryIndexStore) IndexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int) {
if len(fields) == 0 || record == nil {
return
}
if partition == "" {
partition = DefaultPartition
}
s.mu.Lock()
defer s.mu.Unlock()
for _, field := range fields {
val := getFieldValue(record, field)
key := s.indexKey(collection, field)
if s.index[key] == nil {
s.index[key] = make(map[string][]int)
s.index[key] = make(map[string][]IndexPosition)
}
s.index[key][val] = append(s.index[key][val], lineIndex)
s.index[key][val] = append(s.index[key][val], IndexPosition{Partition: partition, LineIndex: lineIndex})
}
}
// UnindexRecord удаляет запись из индекса.
func (s *InMemoryIndexStore) UnindexRecord(collection string, fields []string, record map[string]any, lineIndex int) {
// UnindexRecord удаляет запись из индекса (по partition и lineIndex).
func (s *InMemoryIndexStore) UnindexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int) {
if len(fields) == 0 || record == nil {
return
}
if partition == "" {
partition = DefaultPartition
}
s.mu.Lock()
defer s.mu.Unlock()
for _, field := range fields {
@@ -112,23 +129,23 @@ func (s *InMemoryIndexStore) UnindexRecord(collection string, fields []string, r
if bucket == nil {
continue
}
idxs := bucket[val]
newIdxs := make([]int, 0, len(idxs))
for _, i := range idxs {
if i != lineIndex {
newIdxs = append(newIdxs, i)
positions := bucket[val]
newPos := make([]IndexPosition, 0, len(positions))
for _, p := range positions {
if p.Partition != partition || p.LineIndex != lineIndex {
newPos = append(newPos, p)
}
}
if len(newIdxs) == 0 {
if len(newPos) == 0 {
delete(bucket, val)
} else {
bucket[val] = newIdxs
bucket[val] = newPos
}
}
}
// Lookup возвращает индексы строк по полю и значению.
func (s *InMemoryIndexStore) Lookup(collection, field, value string) ([]int, error) {
// Lookup возвращает позиции (partition, lineIndex) по полю и значению.
func (s *InMemoryIndexStore) Lookup(collection, field, value string) ([]IndexPosition, error) {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -137,27 +154,46 @@ func (s *InMemoryIndexStore) Lookup(collection, field, value string) ([]int, err
if bucket == nil {
return nil, nil
}
indexes := bucket[value]
if len(indexes) == 0 {
positions := bucket[value]
if len(positions) == 0 {
return nil, nil
}
// Возвращаем копию, чтобы вызывающий код не модифицировал внутренний срез
out := make([]int, len(indexes))
copy(out, indexes)
out := make([]IndexPosition, len(positions))
copy(out, positions)
return out, nil
}
// Rebuild полностью перестраивает индекс коллекции.
func (s *InMemoryIndexStore) Rebuild(collection string, fields []string, records []any) error {
// Rebuild перестраивает вклад партиции в индекс.
func (s *InMemoryIndexStore) Rebuild(collection, partition string, fields []string, records []any) error {
if partition == "" {
partition = DefaultPartition
}
s.mu.Lock()
defer s.mu.Unlock()
// Очищаем старые записи по этой коллекции
// Удаляем старые позиции этой партиции из индекса
for _, field := range fields {
key := s.indexKey(collection, field)
delete(s.index, key)
bucket := s.index[key]
if bucket == nil {
continue
}
for val, positions := range bucket {
newPos := make([]IndexPosition, 0, len(positions))
for _, p := range positions {
if p.Partition != partition {
newPos = append(newPos, p)
}
}
if len(newPos) == 0 {
delete(bucket, val)
} else {
bucket[val] = newPos
}
}
}
// Добавляем новые позиции
for idx, rec := range records {
recMap, ok := rec.(map[string]any)
if !ok {
@@ -167,9 +203,9 @@ func (s *InMemoryIndexStore) Rebuild(collection string, fields []string, records
val := getFieldValue(recMap, field)
key := s.indexKey(collection, field)
if s.index[key] == nil {
s.index[key] = make(map[string][]int)
s.index[key] = make(map[string][]IndexPosition)
}
s.index[key][val] = append(s.index[key][val], idx)
s.index[key][val] = append(s.index[key][val], IndexPosition{Partition: partition, LineIndex: idx})
}
}
return nil
@@ -190,7 +226,7 @@ func (s *InMemoryIndexStore) Clear(collection string) error {
}
// GetSnapshotForTest возвращает копию индекса для тестов. Доступ только при accessKey == "give_me_cache".
// Ключ — "collection:field", значение — map[string][]int (значение поля -> номера строк).
// Ключ — "collection:field", значение — map[string][]IndexPosition.
func (s *InMemoryIndexStore) GetSnapshotForTest(accessKey string) map[string]any {
const testAccessKey = "give_me_cache"
if accessKey != testAccessKey {
@@ -200,11 +236,11 @@ func (s *InMemoryIndexStore) GetSnapshotForTest(accessKey string) map[string]any
defer s.mu.RUnlock()
out := make(map[string]any, len(s.index))
for k, bucket := range s.index {
cp := make(map[string][]int, len(bucket))
for val, idxs := range bucket {
idxs2 := make([]int, len(idxs))
copy(idxs2, idxs)
cp[val] = idxs2
cp := make(map[string][]IndexPosition, len(bucket))
for val, positions := range bucket {
pos2 := make([]IndexPosition, len(positions))
copy(pos2, positions)
cp[val] = pos2
}
out[k] = cp
}