Files
srdb/database.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

258 lines
5.1 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"
"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))
for k, v := range db.tables {
result[k] = v
}
return result
}