Files
srdb/sst/reader.go
bourdon ae87c38776 Initial commit: SRDB - High-performance LSM-Tree database
- Core engine with MemTable, SST, WAL
- B+Tree indexing for SST files  
- Leveled compaction strategy
- Multi-table database management
- Schema validation and secondary indexes
- Query builder with complex conditions
- Web UI with HTMX for data visualization
- Command-line tools for diagnostics
2025-10-08 06:38:28 +08:00

153 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sst
import (
"encoding/json"
"fmt"
"os"
"code.tczkiot.com/srdb/btree"
"github.com/edsrzf/mmap-go"
"github.com/golang/snappy"
)
// Reader SST 文件读取器
type Reader struct {
path string
file *os.File
mmap mmap.MMap
header *Header
btReader *btree.Reader
}
// NewReader 创建 SST 读取器
func NewReader(path string) (*Reader, error) {
// 1. 打开文件
file, err := os.Open(path)
if err != nil {
return nil, err
}
// 2. mmap 映射
mmapData, err := mmap.Map(file, mmap.RDONLY, 0)
if err != nil {
file.Close()
return nil, err
}
// 3. 读取 Header
if len(mmapData) < HeaderSize {
mmapData.Unmap()
file.Close()
return nil, fmt.Errorf("file too small")
}
header := UnmarshalHeader(mmapData[:HeaderSize])
if header == nil || !header.Validate() {
mmapData.Unmap()
file.Close()
return nil, fmt.Errorf("invalid header")
}
// 4. 创建 B+Tree Reader
btReader := btree.NewReader(mmapData, header.RootOffset)
return &Reader{
path: path,
file: file,
mmap: mmapData,
header: header,
btReader: btReader,
}, nil
}
// Get 查询一行数据
func (r *Reader) Get(key int64) (*Row, error) {
// 1. 检查范围
if key < r.header.MinKey || key > r.header.MaxKey {
return nil, fmt.Errorf("key out of range")
}
// 2. 在 B+Tree 中查找
dataOffset, dataSize, found := r.btReader.Get(key)
if !found {
return nil, fmt.Errorf("key not found")
}
// 3. 读取数据
if dataOffset+int64(dataSize) > int64(len(r.mmap)) {
return nil, fmt.Errorf("invalid data offset")
}
compressed := r.mmap[dataOffset : dataOffset+int64(dataSize)]
// 4. 解压缩
var data []byte
var err error
if r.header.Compression == CompressionSnappy {
data, err = snappy.Decode(nil, compressed)
if err != nil {
return nil, err
}
} else {
data = compressed
}
// 5. 反序列化
row, err := decodeRow(data)
if err != nil {
return nil, err
}
return row, nil
}
// GetHeader 获取文件头信息
func (r *Reader) GetHeader() *Header {
return r.header
}
// GetPath 获取文件路径
func (r *Reader) GetPath() string {
return r.path
}
// GetAllKeys 获取文件中所有的 key按顺序
func (r *Reader) GetAllKeys() []int64 {
return r.btReader.GetAllKeys()
}
// Close 关闭读取器
func (r *Reader) Close() error {
if r.mmap != nil {
r.mmap.Unmap()
}
if r.file != nil {
return r.file.Close()
}
return nil
}
// decodeRow 解码行数据
func decodeRow(data []byte) (*Row, error) {
// 尝试使用二进制格式解码
row, err := decodeRowBinary(data)
if err == nil {
return row, nil
}
// 降级到 JSON (兼容旧数据)
var decoded map[string]interface{}
err = json.Unmarshal(data, &decoded)
if err != nil {
return nil, err
}
row = &Row{
Seq: int64(decoded["_seq"].(float64)),
Time: int64(decoded["_time"].(float64)),
Data: decoded["data"].(map[string]interface{}),
}
return row, nil
}