diff --git a/server/controllers/init.go b/server/controllers/init.go index 99cad0e..f879df9 100644 --- a/server/controllers/init.go +++ b/server/controllers/init.go @@ -11,7 +11,7 @@ var ( qs services.QueryService ls services.LocksService rs services.RecordsService - vs services.VariantService + vs services.VariantsService hs *services.HealthService ) @@ -25,6 +25,6 @@ func InitAllComponents(storage *vfs.LogStructuredFS) error { ls = services.NewLocksServiceImpl(storage) qs = services.NewQueryServiceImpl(storage) ts = services.NewTablesServiceImpl(storage) - vs = services.NewVariantServiceImpl(storage) + vs = services.NewVariantsServiceImpl(storage) return nil } diff --git a/server/controllers/records.go b/server/controllers/records.go index 5821e08..fcb034b 100644 --- a/server/controllers/records.go +++ b/server/controllers/records.go @@ -1,13 +1,127 @@ package controllers -import "github.com/gin-gonic/gin" +import ( + "errors" + "net/http" + + "github.com/auula/urnadb/server/response" + "github.com/auula/urnadb/server/services" + "github.com/auula/urnadb/types" + "github.com/auula/urnadb/utils" + "github.com/gin-gonic/gin" +) func GetRecordsController(ctx *gin.Context) { + name := ctx.Param("key") + if !utils.NotNullString(name) { + ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam) + return + } + + record, err := rs.GetRecord(name) + if err != nil { + ctx.IndentedJSON(http.StatusInternalServerError, response.Fail( + err.Error(), + )) + } + ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{ + "record": record, + })) } + +type CreateRecordRequest struct { + Record map[string]any `json:"record" binding:"required"` + TTLSeconds int64 `json:"ttl" binding:"omitempty"` +} + func PutRecordsController(ctx *gin.Context) { + name := ctx.Param("key") + if !utils.NotNullString(name) { + ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam) + return + } + var req CreateRecordRequest + err := ctx.ShouldBindJSON(&req) + if err != nil { + handlerRecordsError(ctx, err) + return + } + + rd := types.AcquireRecord() + rd.Record = req.Record + + defer rd.ReleaseToPool() + + err = rs.CreateRecord(name, rd, req.TTLSeconds) + if err != nil { + handlerRecordsError(ctx, err) + return + } + + ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{ + "message": "record created successfully.", + })) } + func DeleteRecordsController(ctx *gin.Context) { + name := ctx.Param("key") + if !utils.NotNullString(name) { + ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam) + return + } + + err := rs.DeleteRecord(name) + if err != nil { + handlerRecordsError(ctx, err) + return + } + + ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{ + "message": "record deleted successfully.", + })) +} + +type SearchRecordRequest struct { + Column string `json:"column" binding:"required"` +} + +func SearchRecordsController(ctx *gin.Context) { + name := ctx.Param("key") + if !utils.NotNullString(name) { + ctx.IndentedJSON(http.StatusBadRequest, missingKeyParam) + return + } + + var req SearchRecordRequest + err := ctx.ShouldBindJSON(&req) + if err != nil { + handlerRecordsError(ctx, err) + return + } + + res, err := rs.SearchRows(name, req.Column) + if err != nil { + handlerRecordsError(ctx, err) + return + } + + ctx.IndentedJSON(http.StatusOK, response.Ok(gin.H{ + "column": res, + })) +} +func handlerRecordsError(ctx *gin.Context, err error) { + switch { + case errors.Is(err, services.ErrRecordUpdateFailed): + ctx.IndentedJSON(http.StatusConflict, response.Fail(err.Error())) + case errors.Is(err, services.ErrRecordNotFound): + ctx.IndentedJSON(http.StatusNotFound, response.Fail(err.Error())) + case errors.Is(err, services.ErrRecordExpired): + ctx.IndentedJSON(http.StatusGone, response.Fail(err.Error())) + default: + // 所有其他错误都统一返回 500 内部服务器错误 + ctx.IndentedJSON(http.StatusInternalServerError, response.Fail(err.Error())) + } } diff --git a/server/routes/api.go b/server/routes/api.go index 12f3565..ede9238 100644 --- a/server/routes/api.go +++ b/server/routes/api.go @@ -57,6 +57,7 @@ func SetupRoutes() *gin.Engine { { records.GET("/:key", controllers.GetRecordsController) records.PUT("/:key", controllers.PutRecordsController) + records.POST("/:key", controllers.SearchRecordsController) records.DELETE("/:key", controllers.DeleteRecordsController) } diff --git a/server/server.go b/server/server.go index 4e5fc2a..91f2eed 100644 --- a/server/server.go +++ b/server/server.go @@ -58,7 +58,7 @@ func init() { } // const local IPv4 address - ipv4, err = getIPv4Address(addrs) + ipv4, err = detectIPv4(addrs) if err != nil { clog.Errorf("Get server IPv4 address failed: %s", err) } @@ -204,8 +204,9 @@ func closeStorage() error { return nil } -func getIPv4Address(addrs []net.Interface) (string, error) { +func detectIPv4(addrs []net.Interface) (string, error) { ip := "" + for _, face := range addrs { adders, err := face.Addrs() if err != nil { @@ -213,14 +214,15 @@ func getIPv4Address(addrs []net.Interface) (string, error) { } for _, addr := range adders { - if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { + ipNet, ok := addr.(*net.IPNet) + if ok && !ipNet.IP.IsLoopback() { if ipNet.IP.To4() != nil { ip = ipNet.IP.String() break } } - } } + return ip, nil } diff --git a/server/server_test.go b/server/server_test.go index c25ef48..c1e61a8 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -164,14 +164,14 @@ func TestHttpServer_Shutdown(t *testing.T) { // 测试 getIPv4Address 函数 func TestGetIPv4Address_EmptyInterfaces(t *testing.T) { - result, err := getIPv4Address([]net.Interface{}) + result, err := detectIPv4([]net.Interface{}) assert.NoError(t, err) assert.Equal(t, "", result) } func TestGetIPv4Address_RealInterfaces(t *testing.T) { interfaces, _ := net.Interfaces() - result, err := getIPv4Address(interfaces) + result, err := detectIPv4(interfaces) assert.NoError(t, err) // 结果可能为空字符串 if result != "" { @@ -192,7 +192,7 @@ func TestInitIPv4Logic(t *testing.T) { // 测试正常情况 interfaces, err := net.Interfaces() if err == nil { - result, err := getIPv4Address(interfaces) + result, err := detectIPv4(interfaces) assert.NoError(t, err) // 验证结果是有效的 IP 地址或空字符串 if result != "" { diff --git a/server/services/health.go b/server/services/health.go index 6e3c0cc..46581d8 100644 --- a/server/services/health.go +++ b/server/services/health.go @@ -14,8 +14,11 @@ type HealthService struct { func NewHealthService(storage *vfs.LogStructuredFS) *HealthService { mem, _ := mem.VirtualMemory() - disk, _ := disk.Usage(storage.GetDirectory()) - return &HealthService{mem: mem, disk: disk, storage: storage} + var diskUsage *disk.UsageStat + if storage != nil { + diskUsage, _ = disk.Usage(storage.GetDirectory()) + } + return &HealthService{mem: mem, disk: diskUsage, storage: storage} } func (h *HealthService) RegionCompactStatus() uint8 { diff --git a/server/services/locks.go b/server/services/locks.go index 90fc4ec..62acdeb 100644 --- a/server/services/locks.go +++ b/server/services/locks.go @@ -51,7 +51,7 @@ func (s *LeaseLockService) ReleaseLock(name string, token string) error { _, seg, err := s.storage.FetchSegment(name) if err != nil { s.acquireLeaseLock(name).Unlock() - clog.Errorf("Locks service release lock: %v", err) + clog.Errorf("[LocksService.ReleaseLock] %v", err) return err } @@ -59,7 +59,7 @@ func (s *LeaseLockService) ReleaseLock(name string, token string) error { if err != nil { seg.ReleaseToPool() s.acquireLeaseLock(name).Unlock() - clog.Errorf("Locks service release lock: %v", err) + clog.Errorf("[LocksService.ReleaseLock] %v", err) return err } @@ -73,7 +73,7 @@ func (s *LeaseLockService) ReleaseLock(name string, token string) error { err = s.storage.DeleteSegment(name) if err != nil { s.acquireLeaseLock(name).Unlock() - clog.Errorf("Locks service release lock: %v", err) + clog.Errorf("[LocksService.ReleaseLock] %v", err) return err } @@ -101,7 +101,7 @@ func (s *LeaseLockService) AcquireLock(name string, ttl int64) (*types.LeaseLock seg, err := vfs.AcquirePoolSegment(name, lease, ttl) if err != nil { utils.ReleaseToPool(lease) - clog.Errorf("Locks service acquire lock: %v", err) + clog.Errorf("[LocksService.AcquireLock] %v", err) return nil, err } @@ -109,7 +109,7 @@ func (s *LeaseLockService) AcquireLock(name string, ttl int64) (*types.LeaseLock err = s.storage.PutSegment(name, seg) if err != nil { utils.ReleaseToPool(lease, seg) - clog.Errorf("Locks service acquire lock: %v", err) + clog.Errorf("[LocksService.AcquireLock] %v", err) return nil, err } @@ -130,14 +130,14 @@ func (s *LeaseLockService) DoLeaseLock(name string, token string) (*types.LeaseL _, seg, err := s.storage.FetchSegment(name) if err != nil { - clog.Errorf("Locks service do lease lock: %v", err) + clog.Errorf("[LocksService.DoLeaseLock] %v", err) return nil, err } old, err := seg.ToLeaseLock() if err != nil { seg.ReleaseToPool() - clog.Errorf("Locks service do lease lock: %v", err) + clog.Errorf("[LocksService.DoLeaseLock] %v", err) return nil, err } @@ -159,13 +159,13 @@ func (s *LeaseLockService) DoLeaseLock(name string, token string) (*types.LeaseL newseg, err := vfs.AcquirePoolSegment(name, newlease, newttl) if err != nil { utils.ReleaseToPool(newlease) - clog.Errorf("Locks service do lease lock: %v", err) + clog.Errorf("[LocksService.DoLeaseLock] %v", err) return nil, err } err = s.storage.PutSegment(name, newseg) if err != nil { - clog.Errorf("Locks service do lease lock: %v", err) + clog.Errorf("[LocksService.DoLeaseLock] %v", err) return nil, err } diff --git a/server/services/records.go b/server/services/records.go index 5c938ff..55fdfe7 100644 --- a/server/services/records.go +++ b/server/services/records.go @@ -4,13 +4,16 @@ import ( "errors" "sync" + "github.com/auula/urnadb/clog" "github.com/auula/urnadb/types" + "github.com/auula/urnadb/utils" "github.com/auula/urnadb/vfs" ) var ( ErrRecordUpdateFailed = errors.New("failed to update record") ErrRecordNotFound = errors.New("record not found") + ErrRecordExpired = errors.New("record ttl is invalid or expired") ) // Record 通常直接映射编程语言中的 class 的一条记录, @@ -24,7 +27,7 @@ type RecordsService interface { // 删除一条名为 name 的记录 DeleteRecord(name string) error // 根据记录名获取到这条记录 - QueryRecord(name string) (*types.Record, error) + GetRecord(name string) (*types.Record, error) // Record 一段创建就不可以更改其内容,要更改直接 PUT 新 Record 和 RUW 操作 // // 更新记录中的某个字段 // PatchRows(name string, data map[string]any) error @@ -33,7 +36,7 @@ type RecordsService interface { // 创建一条名为 name 的记录 CreateRecord(name string, record *types.Record, ttl int64) error // 根据字段搜索一条记录下的某个字段 - SearchRows(name string, column string) (map[string]any, error) + SearchRows(name string, column string) (any, error) } type RecordsServiceImpl struct { @@ -49,25 +52,86 @@ func (rs *RecordsServiceImpl) acquireRecordLock(name string) *sync.RWMutex { // 创建记录 func (rs *RecordsServiceImpl) CreateRecord(name string, record *types.Record, ttl int64) error { - return nil + rs.acquireRecordLock(name).Lock() + defer rs.acquireRecordLock(name).Unlock() + + seg, err := vfs.AcquirePoolSegment(name, record, ttl) + if err != nil { + clog.Errorf("[RecordsService.CreateRecord] %v", err) + return err + } + + defer seg.ReleaseToPool() + + return rs.storage.PutSegment(name, seg) } // 查询记录 -func (rs *RecordsServiceImpl) QueryRecord(name string) (*types.Record, error) { - return nil, nil +func (rs *RecordsServiceImpl) GetRecord(name string) (*types.Record, error) { + if !rs.storage.IsActive(name) { + return nil, ErrRecordNotFound + } + + rs.acquireRecordLock(name).Lock() + defer rs.acquireRecordLock(name).Unlock() + + _, seg, err := rs.storage.FetchSegment(name) + if err != nil { + clog.Errorf("[RecordsService.GetRecord] %v", err) + return nil, err + } + + return seg.ToRecord() } // 删除记录 func (rs *RecordsServiceImpl) DeleteRecord(name string) error { + if !rs.storage.IsActive(name) { + return ErrRecordNotFound + } + + rs.acquireRecordLock(name).Lock() + + err := rs.storage.DeleteSegment(name) + if err != nil { + rs.acquireRecordLock(name).Unlock() + clog.Errorf("[RecordsService.DeleteRecord] %v", err) + return err + } + + rs.acquireRecordLock(name).Unlock() + rs.rlock.Delete(name) + return nil } // 根据条件查询字段(简单示例,只支持一层 map) -func (rs *RecordsServiceImpl) SearchRows(name string, column string) (map[string]any, error) { +func (rs *RecordsServiceImpl) SearchRows(name string, column string) (any, error) { + if !rs.storage.IsActive(name) { + return nil, ErrRecordNotFound + } + rs.acquireRecordLock(name).RLock() defer rs.acquireRecordLock(name).RUnlock() - return nil, nil + _, seg, err := rs.storage.FetchSegment(name) + if err != nil { + clog.Errorf("[RecordsService.SearchRows] %v", err) + return nil, err + } + + record, err := seg.ToRecord() + if err != nil { + clog.Errorf("[RecordsService.SearchRows] %v", err) + return nil, err + } + + defer utils.ReleaseToPool(seg, record) + + // 递归深度搜索 + result := record.SearchItem(column) + + return result, nil } func NewRecordsService(storage *vfs.LogStructuredFS) RecordsService { diff --git a/server/services/tables.go b/server/services/tables.go index 696fd79..353c0c8 100644 --- a/server/services/tables.go +++ b/server/services/tables.go @@ -53,7 +53,7 @@ func (t *TablesServiceImpl) GetTable(name string) (*types.Table, error) { _, seg, err := t.storage.FetchSegment(name) if err != nil { - clog.Errorf("Tables service get: %v", err) + clog.Errorf("[TablesService.GetTable] %v", err) return nil, ErrTableNotFound } @@ -66,7 +66,7 @@ func (t *TablesServiceImpl) DeleteTable(name string) error { err := t.storage.DeleteSegment(name) if err != nil { t.acquireTablesLock(name).Unlock() - clog.Errorf("Tables service delete: %v", err) + clog.Errorf("[TablesService.DeleteTable] %v", err) return err } @@ -87,7 +87,7 @@ func (s *TablesServiceImpl) RemoveRows(name string, condtitons map[string]any) e tab, err := seg.ToTable() if err != nil { - clog.Errorf("Tables service remove rows: %v", err) + clog.Errorf("[TablesService.RemoveRows] %v", err) return err } @@ -103,7 +103,7 @@ func (s *TablesServiceImpl) RemoveRows(name string, condtitons map[string]any) e seg, err = vfs.AcquirePoolSegment(name, tab, ttl) if err != nil { - clog.Errorf("Tables service remove rows: %v", err) + clog.Errorf("[TablesService.RemoveRows] %v", err) return err } @@ -120,7 +120,7 @@ func (s *TablesServiceImpl) CreateTable(name string, table *types.Table, ttl int seg, err := vfs.AcquirePoolSegment(name, table, ttl) if err != nil { - clog.Errorf("Tables service create: %v", err) + clog.Errorf("[TablesService.CreateTable] %v", err) return err } @@ -140,7 +140,7 @@ func (s *TablesServiceImpl) InsertRows(name string, rows map[string]any) (uint32 tab, err := seg.ToTable() if err != nil { - clog.Errorf("Tables service insert rows: %v", err) + clog.Errorf("[TablesService.InsertRows] %v", err) return 0, err } @@ -156,13 +156,13 @@ func (s *TablesServiceImpl) InsertRows(name string, rows map[string]any) (uint32 seg, err = vfs.AcquirePoolSegment(name, tab, ttl) if err != nil { - clog.Errorf("Tables service insert rows: %v", err) + clog.Errorf("[TablesService.InsertRows] %v", err) return 0, err } err = s.storage.PutSegment(name, seg) if err != nil { - clog.Errorf("Tables service insert rows: %v", err) + clog.Errorf("[TablesService.InsertRows] %v", err) return 0, err } @@ -180,7 +180,7 @@ func (s *TablesServiceImpl) PatchRows(name string, conditions, data map[string]a tab, err := seg.ToTable() if err != nil { - clog.Errorf("Tables service patch rows: %v", err) + clog.Errorf("[TablesService.PatchRows] %v", err) return err } @@ -189,7 +189,7 @@ func (s *TablesServiceImpl) PatchRows(name string, conditions, data map[string]a // 根据条件来更新,可以是基于默认的 t_id 和类似于 SQL 条件的 err = tab.UpdateRows(conditions, data) if err != nil { - clog.Errorf("Tables service patch rows: %v", err) + clog.Errorf("[TablesService.PatchRows] %v", err) return err } @@ -200,7 +200,7 @@ func (s *TablesServiceImpl) PatchRows(name string, conditions, data map[string]a seg, err = vfs.AcquirePoolSegment(name, tab, ttl) if err != nil { - clog.Errorf("Tables service patch rows: %v", err) + clog.Errorf("[TablesService.PatchRows] %v", err) return err } @@ -213,13 +213,13 @@ func (s *TablesServiceImpl) QueryRows(name string, wheres map[string]any) ([]map _, seg, err := s.storage.FetchSegment(name) if err != nil { - clog.Errorf("Tables service query rows: %v", err) + clog.Errorf("[TablesService.QueryRows] %v", err) return nil, err } tab, err := seg.ToTable() if err != nil { - clog.Errorf("Tables service query rows: %v", err) + clog.Errorf("[TablesService.QueryRows] %v", err) return nil, err } diff --git a/server/services/variants.go b/server/services/variants.go index 2535862..20053fb 100644 --- a/server/services/variants.go +++ b/server/services/variants.go @@ -17,38 +17,38 @@ var ( // 如果 Number 类型要完成类似于 redis 的 increment 的操作, // 客户端只需要发生算数运输的偏移量即可,最终操作中服务器端完成运算和持久化。 -type VariantService interface { +type VariantsService interface { GetVariant(name string) (*types.Variant, error) SetVariant(name string, value *types.Variant, ttl int64) error Increment(name string, delta float64) (float64, error) DeleteVariant(name string) error } -func (vs *VariantServiceImpl) acquireVariantLock(key string) *sync.RWMutex { +func (vs *VariantsServiceImpl) acquireVariantLock(key string) *sync.RWMutex { actual, _ := vs.vlock.LoadOrStore(key, new(sync.RWMutex)) return actual.(*sync.RWMutex) } -type VariantServiceImpl struct { +type VariantsServiceImpl struct { storage *vfs.LogStructuredFS vlock sync.Map } // 构造函数 - 需要指定类型参数 -func NewVariantServiceImpl(storage *vfs.LogStructuredFS) VariantService { - return &VariantServiceImpl{ +func NewVariantsServiceImpl(storage *vfs.LogStructuredFS) VariantsService { + return &VariantsServiceImpl{ storage: storage, } } // GetVariant 获取变量值 -func (vs *VariantServiceImpl) GetVariant(name string) (*types.Variant, error) { +func (vs *VariantsServiceImpl) GetVariant(name string) (*types.Variant, error) { vs.acquireVariantLock(name).RLock() defer vs.acquireVariantLock(name).RUnlock() _, seg, err := vs.storage.FetchSegment(name) if err != nil { - clog.Errorf("Variant service get value: %v", err) + clog.Errorf("[VariantsService.GetVariant] %v", err) return nil, err } @@ -56,13 +56,13 @@ func (vs *VariantServiceImpl) GetVariant(name string) (*types.Variant, error) { } // SetVariant 设置变量值 -func (vs *VariantServiceImpl) SetVariant(name string, value *types.Variant, ttl int64) error { +func (vs *VariantsServiceImpl) SetVariant(name string, value *types.Variant, ttl int64) error { vs.acquireVariantLock(name).Lock() defer vs.acquireVariantLock(name).Unlock() seg, err := vfs.AcquirePoolSegment(name, value, ttl) if err != nil { - clog.Errorf("Variant service set value: %v", err) + clog.Errorf("[VariantsService.SetVariant] %v", err) return err } @@ -72,7 +72,7 @@ func (vs *VariantServiceImpl) SetVariant(name string, value *types.Variant, ttl } // Increment 增量操作 - 只对数值类型有效 -func (vs *VariantServiceImpl) Increment(name string, delta float64) (float64, error) { +func (vs *VariantsServiceImpl) Increment(name string, delta float64) (float64, error) { if !vs.storage.IsActive(name) { return 0, ErrVariantNotFound } @@ -82,13 +82,13 @@ func (vs *VariantServiceImpl) Increment(name string, delta float64) (float64, er _, seg, err := vs.storage.FetchSegment(name) if err != nil { - clog.Errorf("Variant service incremnt: %v", err) + clog.Errorf("[VariantsService.Increment] %v", err) return 0, err } variant, err := seg.ToVariant() if err != nil { - clog.Errorf("Variant service incremnt: %v", err) + clog.Errorf("[VariantsService.Increment] %v", err) return 0, err } @@ -108,20 +108,20 @@ func (vs *VariantServiceImpl) Increment(name string, delta float64) (float64, er seg, err = vfs.AcquirePoolSegment(name, variant, ttl) if err != nil { - clog.Errorf("Variant service incremnt: %v", err) + clog.Errorf("[VariantsService.Increment] %v", err) return 0, err } err = vs.storage.PutSegment(name, seg) if err != nil { - clog.Errorf("Variant service incremnt: %v", err) + clog.Errorf("[VariantsService.Increment] %v", err) return 0, err } return res_num, nil } -func (vs *VariantServiceImpl) DeleteVariant(name string) error { +func (vs *VariantsServiceImpl) DeleteVariant(name string) error { if !vs.storage.IsActive(name) { return ErrVariantNotFound } @@ -130,7 +130,7 @@ func (vs *VariantServiceImpl) DeleteVariant(name string) error { err := vs.storage.DeleteSegment(name) if err != nil { - clog.Errorf("Variant service delete: %v", err) + clog.Errorf("[VariantsService.DeleteVariant] %v", err) return err } diff --git a/types/record.go b/types/record.go index 70033ad..bf69c99 100644 --- a/types/record.go +++ b/types/record.go @@ -87,17 +87,17 @@ func (rc *Record) DeepMerge(news map[string]interface{}) { } // 从 Tables 查找出键为目标 key 的值,包括所有值中值 -// func (tab *Table) SearchItem(key string) any { -// var results []any -// if items, exists := tab.Table[key]; exists { -// results = append(results, items) -// } - -// for _, item := range tab.Table { -// if innerMap, ok := item.(map[string]any); ok { -// results = append(results, utils.SearchInMap(innerMap, key)...) -// } -// } - -// return results -// } +func (rc *Record) SearchItem(key string) any { + var results []any + if items, exists := rc.Record[key]; exists { + results = append(results, items) + } + + for _, item := range rc.Record { + if innerMap, ok := item.(map[string]any); ok { + results = append(results, utils.SearchInMap(innerMap, key)...) + } + } + + return results +} diff --git a/types/record_test.go b/types/record_test.go index ab1254f..085c9e2 100644 --- a/types/record_test.go +++ b/types/record_test.go @@ -1 +1,183 @@ package types + +import ( + "encoding/json" + "testing" +) + +func TestNewRecord(t *testing.T) { + record := NewRecord() + if record == nil { + t.Error("NewRecord should not return nil") + } + if record.Record == nil { + t.Error("Record.Record should be initialized") + } + if len(record.Record) != 0 { + t.Error("New record should be empty") + } +} + +func TestAcquireRecord(t *testing.T) { + record := AcquireRecord() + if record == nil { + t.Error("AcquireRecord should not return nil") + } + record.ReleaseToPool() +} + +func TestRecord_AddRecord(t *testing.T) { + record := NewRecord() + record.AddRecord("key1", "value1") + record.AddRecord("key2", 123) + + if record.Record["key1"] != "value1" { + t.Errorf("Expected value1, got %v", record.Record["key1"]) + } + if record.Record["key2"] != 123 { + t.Errorf("Expected 123, got %v", record.Record["key2"]) + } +} + +func TestRecord_Size(t *testing.T) { + record := NewRecord() + if record.Size() != 0 { + t.Errorf("Expected size 0, got %d", record.Size()) + } + + record.AddRecord("key1", "value1") + if record.Size() != 1 { + t.Errorf("Expected size 1, got %d", record.Size()) + } + + record.AddRecord("key2", "value2") + if record.Size() != 2 { + t.Errorf("Expected size 2, got %d", record.Size()) + } +} + +func TestRecord_Clear(t *testing.T) { + record := NewRecord() + record.AddRecord("key1", "value1") + record.AddRecord("key2", "value2") + + record.Clear() + if record.Size() != 0 { + t.Errorf("Expected size 0 after clear, got %d", record.Size()) + } +} + +func TestRecord_ToJSON(t *testing.T) { + record := NewRecord() + record.AddRecord("name", "John") + record.AddRecord("age", 30) + + data, err := record.ToJSON() + if err != nil { + t.Errorf("ToJSON failed: %v", err) + } + + var result map[string]any + err = json.Unmarshal(data, &result) + if err != nil { + t.Errorf("JSON unmarshal failed: %v", err) + } + + if result["name"] != "John" || result["age"].(float64) != 30 { + t.Errorf("JSON data mismatch: %v", result) + } +} + +func TestRecord_ToBytes(t *testing.T) { + record := NewRecord() + record.AddRecord("name", "John") + record.AddRecord("age", 30) + + data, err := record.ToBytes() + if err != nil { + t.Errorf("ToBytes failed: %v", err) + } + if len(data) == 0 { + t.Error("ToBytes should return non-empty data") + } +} + +func TestRecord_DeepMerge(t *testing.T) { + record := NewRecord() + record.AddRecord("user", map[string]interface{}{ + "name": "John", + "age": 30, + }) + + newData := map[string]interface{}{ + "user": map[string]interface{}{ + "age": 31, + "email": "john@example.com", + }, + "status": "active", + } + + record.DeepMerge(newData) + + user := record.Record["user"].(map[string]interface{}) + if user["name"] != "John" { + t.Errorf("Expected name John, got %v", user["name"]) + } + if user["age"] != 31 { + t.Errorf("Expected age 31, got %v", user["age"]) + } + if user["email"] != "john@example.com" { + t.Errorf("Expected email john@example.com, got %v", user["email"]) + } + if record.Record["status"] != "active" { + t.Errorf("Expected status active, got %v", record.Record["status"]) + } +} + +func TestRecord_SearchItem(t *testing.T) { + record := NewRecord() + + record.AddRecord("name", "John") + record.AddRecord("age", 30) + + result := record.SearchItem("name") + if len(result.([]any)) != 1 || result.([]any)[0] != "John" { + t.Errorf("Expected [John], got %v", result) + } + + record.AddRecord("user", map[string]any{ + "name": "Alice", + "profile": map[string]any{ + "name": "Alice Profile", + "age": 25, + }, + }) + + result = record.SearchItem("name") + results := result.([]any) + if len(results) != 3 { + t.Errorf("Expected 3 results, got %d", len(results)) + } + + result = record.SearchItem("nonexistent") + if len(result.([]any)) != 0 { + t.Errorf("Expected empty result, got %v", result) + } + + emptyRecord := NewRecord() + result = emptyRecord.SearchItem("any") + if len(result.([]any)) != 0 { + t.Errorf("Expected empty result for empty record, got %v", result) + } +} + +func TestRecord_ReleaseToPool(t *testing.T) { + record := AcquireRecord() + record.AddRecord("test", "value") + + record.ReleaseToPool() + + if record.Size() != 0 { + t.Error("Record should be cleared after release to pool") + } +} diff --git a/vfs/lfs.go b/vfs/lfs.go index 401e27f..fedcc40 100644 --- a/vfs/lfs.go +++ b/vfs/lfs.go @@ -205,7 +205,7 @@ func (lfs *LogStructuredFS) IsActive(key string) bool { return false } - return inode != nil && time.Now().UnixMicro() < inode.ExpiredAt + return (inode != nil && inode.ExpiredAt == ImmortalTTL) || (inode.ExpiredAt > 0 && time.Now().UnixMicro() < inode.ExpiredAt) } func (lfs *LogStructuredFS) FetchSegment(key string) (uint64, *Segment, error) { diff --git a/vfs/lfs_test.go b/vfs/lfs_test.go index 1115c61..0d1d30b 100644 --- a/vfs/lfs_test.go +++ b/vfs/lfs_test.go @@ -530,7 +530,7 @@ func TestVFSOpertions(t *testing.T) { err = fss.PutSegment("key-01", seg) assert.NoError(t, err) - assert.NotEqual(t, fss.IsActive("key-01"), true) + assert.Equal(t, fss.IsActive("key-01"), true) assert.Equal(t, fss.RefreshInodeCount(), uint64(1))