Files
srdb/database.go
bourdon 23843493b8 重构代码结构并添加完整功能
主要改动:
- 重构目录结构:合并子目录到根目录,简化项目结构
- 添加完整的查询 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 优化:提取共享样式,减少重复代码
- 清理遗留代码:删除未使用的方法和样式
2025-10-08 23:04:47 +08:00

257 lines
5.0 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 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
}