重构代码结构并添加完整功能

主要改动:
- 重构目录结构:合并子目录到根目录,简化项目结构
- 添加完整的查询 API:支持复杂条件查询、字段选择、游标模式
- 实现 LSM-Tree Compaction:7层结构、Score-based策略、后台异步合并
- 添加 Web UI:基于 Lit 的现代化管理界面,支持数据浏览和 Manifest 查看
- 完善文档:添加 README.md 和 examples/webui/README.md

新增功能:
- Query Builder:链式查询 API,支持 Eq/Lt/Gt/In/Between/Contains 等操作符
- Web UI 组件:srdb-app、srdb-table-list、srdb-data-view、srdb-manifest-view 等
- 列选择持久化:自动保存到 localStorage
- 刷新按钮:一键刷新当前视图
- 主题切换:深色/浅色主题支持

代码优化:
- 使用 Go 1.24 新特性:range 7、min()、maps.Copy()、slices.Sort()
- 统一组件命名:所有 Web Components 使用 srdb-* 前缀
- CSS 优化:提取共享样式,减少重复代码
- 清理遗留代码:删除未使用的方法和样式
This commit is contained in:
2025-10-08 16:42:31 +08:00
parent ae87c38776
commit 23843493b8
64 changed files with 8374 additions and 6396 deletions

129
query.go
View File

@@ -3,9 +3,9 @@ package srdb
import (
"encoding/json"
"fmt"
"maps"
"slices"
"strings"
"code.tczkiot.com/srdb/sst"
)
type Fieldset interface {
@@ -489,29 +489,101 @@ func (qb *QueryBuilder) Rows() (*Rows, error) {
visited: make(map[int64]bool),
}
// 初始化 Active MemTable 迭代器
// 收集所有数据源的 keys 并全局排序
// 立即读取数据避免 compaction 期间文件被删除
keyToRow := make(map[int64]*SSTableRow) // 存储已读取的行数据
var allKeys []int64
// 1. 从 Active MemTable 读取数据
activeMemTable := qb.engine.memtableManager.GetActive()
if activeMemTable != nil {
activeKeys := activeMemTable.Keys()
if len(activeKeys) > 0 {
rows.memIterator = newMemtableIterator(activeKeys)
for _, key := range activeKeys {
if data, ok := activeMemTable.Get(key); ok {
var row SSTableRow
if err := json.Unmarshal(data, &row); err == nil {
keyToRow[key] = &row
allKeys = append(allKeys, key)
}
}
}
}
// 准备 Immutable MemTables(延迟初始化)
rows.immutableIndex = 0
// 2. 从所有 Immutable MemTables 读取数据
immutables := qb.engine.memtableManager.GetImmutables()
for _, imm := range immutables {
immKeys := imm.MemTable.Keys()
for _, key := range immKeys {
// 如果 key 已存在(来自更新的数据源),跳过
if _, exists := keyToRow[key]; exists {
continue
}
// 初始化 SST 文件 readers
if data, ok := imm.MemTable.Get(key); ok {
var row SSTableRow
if err := json.Unmarshal(data, &row); err == nil {
keyToRow[key] = &row
allKeys = append(allKeys, key)
}
}
}
}
// 3. 收集所有 SST 文件的 keys
sstReaders := qb.engine.sstManager.GetReaders()
for _, reader := range sstReaders {
// 获取文件中实际存在的 key 列表(已排序)
// 这比 minKey→maxKey 逐个尝试高效 100-1000 倍(对于稀疏 key
// 获取文件中实际存在的 key 列表(已在 GetAllKeys 中排序)
keys := reader.GetAllKeys()
rows.sstReaders = append(rows.sstReaders, &sstReader{
reader: reader,
keys: keys,
index: 0,
})
// 记录所有 keys实际数据稍后统一从 engine 读取)
for _, key := range keys {
// 如果 key 已存在(来自更新的数据源),跳过
if _, exists := keyToRow[key]; !exists {
allKeys = append(allKeys, key)
keyToRow[key] = nil // 占位,表示需要读取
}
}
}
// 4. 对所有 keys 排序
if len(allKeys) > 0 {
// 去重(使用 map 已经去重了,但 allKeys 可能有重复)
keySet := make(map[int64]bool)
uniqueKeys := make([]int64, 0, len(allKeys))
for _, key := range allKeys {
if !keySet[key] {
keySet[key] = true
uniqueKeys = append(uniqueKeys, key)
}
}
// 排序
slices.Sort(uniqueKeys)
// 统一从 engine 读取所有数据(避免 compaction 导致的文件删除)
rows.cachedRows = make([]*SSTableRow, 0, len(uniqueKeys))
for _, seq := range uniqueKeys {
// 如果已经从 MemTable 读取,直接使用
row := keyToRow[seq]
if row == nil {
// 从 engine 读取(会搜索 MemTable + 所有 SST包括 compaction 后的新文件)
var err error
row, err = qb.engine.Get(seq)
if err != nil {
// 数据不存在(理论上不应该发生,因为 key 来自索引)
continue
}
}
if qb.Match(row.Data) {
rows.cachedRows = append(rows.cachedRows, row)
}
}
// 使用缓存模式
rows.cached = true
rows.cachedIndex = -1
}
return rows, nil
@@ -553,7 +625,7 @@ func (qb *QueryBuilder) Scan(value any) error {
type Row struct {
schema *Schema
fields []string // 要选择的字段nil 表示选择所有字段
inner *sst.Row
inner *SSTableRow
}
// Data 获取行数据(根据 Select 过滤字段)
@@ -563,13 +635,11 @@ func (r *Row) Data() map[string]any {
}
// 如果没有指定字段,返回所有数据(包括 _seq 和 _time
if r.fields == nil || len(r.fields) == 0 {
if len(r.fields) == 0 {
result := make(map[string]any)
result["_seq"] = r.inner.Seq
result["_time"] = r.inner.Time
for k, v := range r.inner.Data {
result[k] = v
}
maps.Copy(result, r.inner.Data)
return result
}
@@ -636,7 +706,7 @@ type Rows struct {
// 缓存模式(用于 Collect/Data 等方法)
cached bool
cachedRows []*sst.Row
cachedRows []*SSTableRow
cachedIndex int // 缓存模式下的迭代位置
}
@@ -663,9 +733,8 @@ func (m *memtableIterator) next() (int64, bool) {
// sstReader 包装 SST Reader 的迭代状态
type sstReader struct {
reader any // 实际的 SST reader
keys []int64 // 文件中实际存在的 key 列表(已排序)
index int // 当前迭代位置
keys []int64 // 文件中实际存在的 key 列表(已排序)
index int // 当前迭代位置
}
// Next 移动到下一行,返回是否还有数据
@@ -769,7 +838,11 @@ func (r *Rows) nextFromCache() bool {
if r.cachedIndex >= len(r.cachedRows) {
return false
}
r.currentRow = &Row{schema: r.schema, fields: r.fields, inner: r.cachedRows[r.cachedIndex]}
r.currentRow = &Row{
schema: r.schema,
fields: r.fields,
inner: r.cachedRows[r.cachedIndex],
}
return true
}
@@ -860,7 +933,11 @@ func (r *Rows) Last() (*Row, error) {
if len(r.cachedRows) == 0 {
return nil, fmt.Errorf("no rows")
}
return &Row{schema: r.schema, fields: r.fields, inner: r.cachedRows[len(r.cachedRows)-1]}, nil
return &Row{
schema: r.schema,
fields: r.fields,
inner: r.cachedRows[len(r.cachedRows)-1],
}, nil
}
// Count 返回总行数(别名)