主要改动: - 重构目录结构:合并子目录到根目录,简化项目结构 - 添加完整的查询 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 优化:提取共享样式,减少重复代码 - 清理遗留代码:删除未使用的方法和样式
257 lines
5.0 KiB
Go
257 lines
5.0 KiB
Go
package srdb
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"maps"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
)
|
||
|
||
// Database 数据库,管理多个表
|
||
type Database struct {
|
||
// 数据库目录
|
||
dir string
|
||
|
||
// 所有表
|
||
tables map[string]*Table
|
||
|
||
// 元数据
|
||
metadata *Metadata
|
||
|
||
// 锁
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// Metadata 数据库元数据
|
||
type Metadata struct {
|
||
Version int `json:"version"`
|
||
Tables []TableInfo `json:"tables"`
|
||
}
|
||
|
||
// TableInfo 表信息
|
||
type TableInfo struct {
|
||
Name string `json:"name"`
|
||
Dir string `json:"dir"`
|
||
CreatedAt int64 `json:"created_at"`
|
||
}
|
||
|
||
// Open 打开数据库
|
||
func Open(dir string) (*Database, error) {
|
||
// 创建目录
|
||
err := os.MkdirAll(dir, 0755)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
db := &Database{
|
||
dir: dir,
|
||
tables: make(map[string]*Table),
|
||
}
|
||
|
||
// 加载元数据
|
||
err = db.loadMetadata()
|
||
if err != nil {
|
||
// 如果元数据不存在,创建新的
|
||
db.metadata = &Metadata{
|
||
Version: 1,
|
||
Tables: make([]TableInfo, 0),
|
||
}
|
||
err = db.saveMetadata()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// 恢复所有表
|
||
err = db.recoverTables()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return db, nil
|
||
}
|
||
|
||
// loadMetadata 加载元数据
|
||
func (db *Database) loadMetadata() error {
|
||
metaPath := filepath.Join(db.dir, "database.meta")
|
||
data, err := os.ReadFile(metaPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
db.metadata = &Metadata{}
|
||
return json.Unmarshal(data, db.metadata)
|
||
}
|
||
|
||
// saveMetadata 保存元数据
|
||
func (db *Database) saveMetadata() error {
|
||
metaPath := filepath.Join(db.dir, "database.meta")
|
||
data, err := json.MarshalIndent(db.metadata, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 原子性写入
|
||
tmpPath := metaPath + ".tmp"
|
||
err = os.WriteFile(tmpPath, data, 0644)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return os.Rename(tmpPath, metaPath)
|
||
}
|
||
|
||
// recoverTables 恢复所有表
|
||
func (db *Database) recoverTables() error {
|
||
var failedTables []string
|
||
|
||
for _, tableInfo := range db.metadata.Tables {
|
||
// FIXME: 是否需要校验 tableInfo.Dir ?
|
||
table, err := openTable(tableInfo.Name, db)
|
||
if err != nil {
|
||
// 记录失败的表,但继续恢复其他表
|
||
failedTables = append(failedTables, tableInfo.Name)
|
||
fmt.Printf("[WARNING] Failed to open table %s: %v\n", tableInfo.Name, err)
|
||
fmt.Printf("[WARNING] Table %s will be skipped. You may need to drop and recreate it.\n", tableInfo.Name)
|
||
continue
|
||
}
|
||
db.tables[tableInfo.Name] = table
|
||
}
|
||
|
||
// 如果有失败的表,输出汇总信息
|
||
if len(failedTables) > 0 {
|
||
fmt.Printf("[WARNING] %d table(s) failed to recover: %v\n", len(failedTables), failedTables)
|
||
fmt.Printf("[WARNING] To fix: Delete the corrupted table directory and restart.\n")
|
||
fmt.Printf("[WARNING] Example: rm -rf %s/<table_name>\n", db.dir)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateTable 创建表
|
||
func (db *Database) CreateTable(name string, schema *Schema) (*Table, error) {
|
||
db.mu.Lock()
|
||
defer db.mu.Unlock()
|
||
|
||
// 检查表是否已存在
|
||
if _, exists := db.tables[name]; exists {
|
||
return nil, fmt.Errorf("table %s already exists", name)
|
||
}
|
||
|
||
// 创建表
|
||
table, err := createTable(name, schema, db)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 添加到 tables map
|
||
db.tables[name] = table
|
||
|
||
// 更新元数据
|
||
db.metadata.Tables = append(db.metadata.Tables, TableInfo{
|
||
Name: name,
|
||
Dir: name,
|
||
CreatedAt: table.createdAt,
|
||
})
|
||
|
||
err = db.saveMetadata()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return table, nil
|
||
}
|
||
|
||
// GetTable 获取表
|
||
func (db *Database) GetTable(name string) (*Table, error) {
|
||
db.mu.RLock()
|
||
defer db.mu.RUnlock()
|
||
|
||
table, exists := db.tables[name]
|
||
if !exists {
|
||
return nil, fmt.Errorf("table %s not found", name)
|
||
}
|
||
|
||
return table, nil
|
||
}
|
||
|
||
// DropTable 删除表
|
||
func (db *Database) DropTable(name string) error {
|
||
db.mu.Lock()
|
||
defer db.mu.Unlock()
|
||
|
||
// 检查表是否存在
|
||
table, exists := db.tables[name]
|
||
if !exists {
|
||
return fmt.Errorf("table %s not found", name)
|
||
}
|
||
|
||
// 关闭表
|
||
err := table.Close()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 从 map 中移除
|
||
delete(db.tables, name)
|
||
|
||
// 删除表目录
|
||
tableDir := filepath.Join(db.dir, name)
|
||
err = os.RemoveAll(tableDir)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 更新元数据
|
||
newTables := make([]TableInfo, 0)
|
||
for _, info := range db.metadata.Tables {
|
||
if info.Name != name {
|
||
newTables = append(newTables, info)
|
||
}
|
||
}
|
||
db.metadata.Tables = newTables
|
||
|
||
return db.saveMetadata()
|
||
}
|
||
|
||
// ListTables 列出所有表
|
||
func (db *Database) ListTables() []string {
|
||
db.mu.RLock()
|
||
defer db.mu.RUnlock()
|
||
|
||
tables := make([]string, 0, len(db.tables))
|
||
for name := range db.tables {
|
||
tables = append(tables, name)
|
||
}
|
||
return tables
|
||
}
|
||
|
||
// Close 关闭数据库
|
||
func (db *Database) Close() error {
|
||
db.mu.Lock()
|
||
defer db.mu.Unlock()
|
||
|
||
// 关闭所有表
|
||
for _, table := range db.tables {
|
||
err := table.Close()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetAllTablesInfo 获取所有表的信息(用于 WebUI)
|
||
func (db *Database) GetAllTablesInfo() map[string]*Table {
|
||
db.mu.RLock()
|
||
defer db.mu.RUnlock()
|
||
|
||
// 返回副本以避免并发问题
|
||
result := make(map[string]*Table, len(db.tables))
|
||
maps.Copy(result, db.tables)
|
||
return result
|
||
}
|