Files
ragflow/internal/storage/memory.go
Zhichang Yu 014c3f634f Align Go ingestion boundaries with Python (#16647)
Moves doc_id blob resolution into Parser, tightens chunker/tokenizer to
Python output_format semantics, updates extractor list handling, and
fixes real-template integration tests.
2026-07-05 20:43:52 +08:00

220 lines
6.1 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package storage
import (
"errors"
"fmt"
"sync"
"time"
)
// ErrMemoryNotFound is returned when a key does not exist in the in-memory backend.
var ErrMemoryNotFound = errors.New("memory storage: object not found")
// MemoryEntry describes a single stored object, used by Inspect for tests and diagnostics.
type MemoryEntry struct {
Bucket string
Key string
Size int
}
// MemoryStorage is an in-process implementation of the Storage interface,
// intended for unit tests and ephemeral tooling. All operations are safe
// for concurrent use.
type MemoryStorage struct {
mu sync.RWMutex
objects map[string]map[string][]byte
}
// NewMemoryStorage returns a fresh, empty in-memory storage backend.
func NewMemoryStorage() Storage {
return &MemoryStorage{objects: make(map[string]map[string][]byte)}
}
// Health always reports healthy for the in-memory backend.
func (m *MemoryStorage) Health() bool {
return true
}
// Put uploads an object to the in-memory backend, creating the bucket
// on demand if it does not yet exist. The stored bytes are a defensive copy.
func (m *MemoryStorage) Put(bucket, fnm string, binary []byte, tenantID ...string) error {
if bucket == "" {
return fmt.Errorf("memory storage: bucket is required")
}
if fnm == "" {
return fmt.Errorf("memory storage: key is required")
}
m.mu.Lock()
defer m.mu.Unlock()
bucketMap, ok := m.objects[bucket]
if !ok {
bucketMap = make(map[string][]byte)
m.objects[bucket] = bucketMap
}
cp := make([]byte, len(binary))
copy(cp, binary)
bucketMap[fnm] = cp
return nil
}
// Get retrieves an object from the in-memory backend. Returns
// ErrMemoryNotFound when the bucket or key is missing.
func (m *MemoryStorage) Get(bucket, fnm string, tenantID ...string) ([]byte, error) {
m.mu.RLock()
defer m.mu.RUnlock()
bucketMap, ok := m.objects[bucket]
if !ok {
return nil, fmt.Errorf("memory storage: bucket %q: %w", bucket, ErrMemoryNotFound)
}
data, ok := bucketMap[fnm]
if !ok {
return nil, fmt.Errorf("memory storage: object %q in bucket %q: %w", fnm, bucket, ErrMemoryNotFound)
}
out := make([]byte, len(data))
copy(out, data)
return out, nil
}
// Remove deletes an object from the in-memory backend. Removing a
// non-existent key is a no-op and returns nil.
func (m *MemoryStorage) Remove(bucket, fnm string, tenantID ...string) error {
m.mu.Lock()
defer m.mu.Unlock()
bucketMap, ok := m.objects[bucket]
if !ok {
return nil
}
delete(bucketMap, fnm)
return nil
}
// ObjExist reports whether the given bucket and key are present.
func (m *MemoryStorage) ObjExist(bucket, fnm string, tenantID ...string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
bucketMap, ok := m.objects[bucket]
if !ok {
return false
}
_, ok = bucketMap[fnm]
return ok
}
// GetPresignedURL returns a deterministic, non-network URL string for tests.
// Format: memory://<bucket>/<key>?exp=<unix-seconds>
func (m *MemoryStorage) GetPresignedURL(bucket, fnm string, expires time.Duration, tenantID ...string) (string, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if _, ok := m.objects[bucket]; !ok {
return "", fmt.Errorf("memory storage: bucket %q: %w", bucket, ErrMemoryNotFound)
}
bucketMap := m.objects[bucket]
if _, ok := bucketMap[fnm]; !ok {
return "", fmt.Errorf("memory storage: object %q in bucket %q: %w", fnm, bucket, ErrMemoryNotFound)
}
exp := time.Now().Add(expires).Unix()
return fmt.Sprintf("memory://%s/%s?exp=%d", bucket, fnm, exp), nil
}
// BucketExists reports whether the named bucket has been created.
func (m *MemoryStorage) BucketExists(bucket string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
_, ok := m.objects[bucket]
return ok
}
// RemoveBucket deletes a bucket and all of its keys. Removing a
// non-existent bucket is a no-op and returns nil.
func (m *MemoryStorage) RemoveBucket(bucket string) error {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.objects, bucket)
return nil
}
// Copy duplicates an object from srcBucket/srcKey to destBucket/destKey.
// The source is left untouched. Returns false if the source does not exist
// or if the destination bucket creation fails.
func (m *MemoryStorage) Copy(srcBucket, srcPath, destBucket, destPath string) bool {
m.mu.RLock()
srcBucketMap, ok := m.objects[srcBucket]
if !ok {
m.mu.RUnlock()
return false
}
data, ok := srcBucketMap[srcPath]
if !ok {
m.mu.RUnlock()
return false
}
cp := make([]byte, len(data))
copy(cp, data)
m.mu.RUnlock()
m.mu.Lock()
defer m.mu.Unlock()
destBucketMap, ok := m.objects[destBucket]
if !ok {
destBucketMap = make(map[string][]byte)
m.objects[destBucket] = destBucketMap
}
destBucketMap[destPath] = cp
return true
}
// Move transfers an object to a new location, deleting the source on success.
// Returns false if the source does not exist or the copy step fails.
func (m *MemoryStorage) Move(srcBucket, srcPath, destBucket, destPath string) bool {
if !m.Copy(srcBucket, srcPath, destBucket, destPath) {
return false
}
if err := m.Remove(srcBucket, srcPath); err != nil {
return false
}
return true
}
// Inspect returns a stable snapshot of all (bucket, key, size) entries
// currently held by the backend. Intended for test diagnostics and
// cleanup assertions. The slice is freshly allocated and safe to mutate.
func (m *MemoryStorage) Inspect() []MemoryEntry {
m.mu.RLock()
defer m.mu.RUnlock()
out := make([]MemoryEntry, 0)
for bucket, bucketMap := range m.objects {
for key, data := range bucketMap {
out = append(out, MemoryEntry{Bucket: bucket, Key: key, Size: len(data)})
}
}
return out
}