Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0a03940
feat: separate persistent and temporary cache storage with individual…
found-cake Jul 31, 2025
b6a7c9a
feat: write transaction
found-cake Jul 31, 2025
f3274c8
feat: read transaction
found-cake Jul 31, 2025
8732b86
refactor: change mux position
found-cake Aug 1, 2025
76ef3ca
refactor: merge persistent and temporary storage maps in snapshot tra…
found-cake Aug 3, 2025
a23a444
refactor: simplify LockReadTransaction by removing redundant lock sta…
found-cake Aug 3, 2025
0f78d81
refactor: use entry.NewEntry in writeTransaction newEntry helper
found-cake Aug 3, 2025
cb0b7a0
refactor: extract snapshotReadTx logic into a constructor function
found-cake Aug 5, 2025
567a66e
Merge branch 'master' into feature/split-cache
found-cake Aug 5, 2025
7152a66
remove: batch operations
found-cake Aug 5, 2025
b71275a
feat: read and write transaction
found-cake Aug 5, 2025
2d296b0
feat: Add type conversion helpers (As*, From*) for Entry
found-cake Aug 5, 2025
2cf8d4a
refactor: Use Entry for Get/Set methods instead of raw types
found-cake Aug 5, 2025
6516ee3
refactor: Use Entry for Get/Set methods instead of raw types
found-cake Aug 5, 2025
4240d5d
lol
found-cake Aug 10, 2025
e95334a
feat: Add CopyData Method
found-cake Aug 10, 2025
e062dd1
refactor: use transaction for numeric increment/decrement
found-cake Aug 10, 2025
81c015c
refactor: methods for split memory db
found-cake Aug 16, 2025
6a977d5
refactor: sqliter load data for split db
found-cake Aug 17, 2025
55b5df5
refactor: store constructor
found-cake Aug 17, 2025
71afae3
fix: dead lock
found-cake Aug 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions entry/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ type Entry struct {
Expiry int64
}

func (e Entry) IsExpired() bool {
func (e *Entry) CopyData() []byte {
result := make([]byte, len(e.Data))
copy(result, e.Data)
return result
}

func (e *Entry) IsExpired() bool {
return e.IsExpiredWithUnixMilli(time.Now().UnixMilli())
}

func (e Entry) IsExpiredWithUnixMilli(now int64) bool {
func (e *Entry) IsExpiredWithUnixMilli(now int64) bool {
return e.Expiry > 0 && e.Expiry <= now
}

Expand Down
23 changes: 23 additions & 0 deletions entry/type_boolean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsBool() (bool, error) {
if e.Type != types.BOOLEAN {
return false, errors.ErrTypeMismatch(types.BOOLEAN, e.Type)
}
return len(e.Data) > 0 && e.Data[0] == 1, nil
}

func FromBool(value bool, exp time.Duration) Entry {
v := byte(0)
if value {
v = 1
}
return NewEntry(types.BOOLEAN, []byte{v}, exp)
}
31 changes: 31 additions & 0 deletions entry/type_float.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsFloat32() (float32, error) {
if e.Type != types.FLOAT32 {
return 0, errors.ErrTypeMismatch(types.FLOAT32, e.Type)
}
return utils.Binary2Float32(e.Data)
}

func FromFloat32(value float32, exp time.Duration) Entry {
return NewEntry(types.FLOAT32, utils.Float32toBinary(value), exp)
}

func (e *Entry) AsFloat64() (float64, error) {
if e.Type != types.FLOAT64 {
return 0, errors.ErrTypeMismatch(types.FLOAT64, e.Type)
}
return utils.Binary2Float64(e.Data)
}

func FromFloat64(key string, value float64, exp time.Duration) Entry {
return NewEntry(types.FLOAT64, utils.Float64toBinary(value), exp)
}
42 changes: 42 additions & 0 deletions entry/type_integer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsInt16() (int16, error) {
if e.Type != types.INT16 {
return 0, errors.ErrTypeMismatch(types.INT16, e.Type)
}
return utils.Binary2Int16(e.Data)
}

func FromInt16(value int16, exp time.Duration) Entry {
return NewEntry(types.INT16, utils.Int16toBinary(value), exp)
}

func (e *Entry) AsInt32() (int32, error) {
if e.Type != types.INT32 {
return 0, errors.ErrTypeMismatch(types.INT32, e.Type)
}
return utils.Binary2Int32(e.Data)
}

func FromInt32(key string, value int32, exp time.Duration) Entry {
return NewEntry(types.INT32, utils.Int32toBinary(value), exp)
}

func (e *Entry) AsInt64() (int64, error) {
if e.Type != types.INT64 {
return 0, errors.ErrTypeMismatch(types.INT64, e.Type)
}
return utils.Binary2Int64(e.Data)
}

func FromInt64(key string, value int64, exp time.Duration) Entry {
return NewEntry(types.INT64, utils.Int64toBinary(value), exp)
}
27 changes: 27 additions & 0 deletions entry/type_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package entry

import (
"encoding/json"
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsJSON(target interface{}) error {
if e.Type != types.JSON {
return errors.ErrTypeMismatch(types.JSON, e.Type)
}
if len(e.Data) == 0 {
return errors.ErrDataEmpty
}
return json.Unmarshal(e.Data, target)
}

func FromJSON(value interface{}, exp time.Duration) (Entry, error) {
if data, err := json.Marshal(value); err != nil {
return Entry{}, err
} else {
return NewEntry(types.JSON, data, exp), nil
}
}
28 changes: 28 additions & 0 deletions entry/type_raw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsRaw() ([]byte, error) {
if e.Type != types.RAW {
return nil, errors.ErrTypeMismatch(types.RAW, e.Type)
}

return e.CopyData(), nil
}

func (e *Entry) AsRawNoCopy() ([]byte, error) {
if e.Type != types.RAW {
return nil, errors.ErrTypeMismatch(types.RAW, e.Type)
}

return e.Data, nil
}

func FromRaw(value []byte, exp time.Duration) Entry {
return NewEntry(types.RAW, value, exp)
}
19 changes: 19 additions & 0 deletions entry/type_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsString() (string, error) {
if e.Type != types.STRING {
return "", errors.ErrTypeMismatch(types.STRING, e.Type)
}
return string(e.Data), nil
}

func FromString(value string, exp time.Duration) Entry {
return NewEntry(types.STRING, []byte(value), exp)
}
28 changes: 28 additions & 0 deletions entry/type_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsTime() (time.Time, error) {
var t time.Time
if e.Type != types.TIME {
return t, errors.ErrTypeMismatch(types.TIME, e.Type)
}
if len(e.Data) == 0 {
return t, errors.ErrDataEmpty
}
err := t.UnmarshalBinary(e.Data)
return t, err
}

func FromTime(value time.Time, exp time.Duration) (Entry, error) {
if b, err := value.MarshalBinary(); err != nil {
return Entry{}, err
} else {
return NewEntry(types.TIME, b, exp), nil
}
}
42 changes: 42 additions & 0 deletions entry/type_uinteger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package entry

import (
"time"

"github.com/found-cake/CacheStore/errors"
"github.com/found-cake/CacheStore/utils"
"github.com/found-cake/CacheStore/utils/types"
)

func (e *Entry) AsUInt16() (uint16, error) {
if e.Type != types.UINT16 {
return 0, errors.ErrTypeMismatch(types.UINT16, e.Type)
}
return utils.Binary2UInt16(e.Data)
}

func FromUInt16(value uint16, exp time.Duration) Entry {
return NewEntry(types.UINT16, utils.UInt16toBinary(value), exp)
}

func (e *Entry) AsUInt32() (uint32, error) {
if e.Type != types.UINT32 {
return 0, errors.ErrTypeMismatch(types.UINT32, e.Type)
}
return utils.Binary2UInt32(e.Data)
}

func FromUInt32(value uint32, exp time.Duration) Entry {
return NewEntry(types.UINT32, utils.UInt32toBinary(value), exp)
}

func (e *Entry) AsUInt64() (uint64, error) {
if e.Type != types.UINT64 {
return 0, errors.ErrTypeMismatch(types.UINT64, e.Type)
}
return utils.Binary2UInt64(e.Data)
}

func FromUInt64(value uint64, exp time.Duration) Entry {
return NewEntry(types.UINT64, utils.UInt64toBinary(value), exp)
}
10 changes: 7 additions & 3 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (

var (
ErrKeyEmpty = errors.New("key cannot be empty")
ErrDataEmpty = errors.New("data cannot be empty")
ErrIsClosed = errors.New("cache store is closed")
ErrAlreadyCommit = errors.New("transaction already committed")
ErrNotLocked = errors.New("read transaction not locked")
ErrValueNil = errors.New("value cannot be null")
ErrDBNotInit = errors.New("database not initialized")
ErrFileNameEmpty = errors.New("filename cannot be empty")
Expand All @@ -28,9 +32,9 @@ func ErrNoDataForKey(key string) error {
return fmt.Errorf("no data found for key: %s", key)
}

func ErrTypeMismatch(key string, expected, actual types.DataType) error {
return fmt.Errorf("type mismatch for key '%s': expected %s, got %s",
key, expected.String(), actual.String())
func ErrTypeMismatch(expected, actual types.DataType) error {
return fmt.Errorf("type mismatch: expected %s, got %s",
expected.String(), actual.String())
}

func ErrUnsignedUnderflow[T generic.Unsigned](key string, current, delta T) error {
Expand Down
20 changes: 14 additions & 6 deletions sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ func NewSqliteStore(filename string) (*SqliteStore, error) {
}, nil
}

func (s *SqliteStore) LoadFromDB() (map[string]entry.Entry, error) {
func (s *SqliteStore) LoadFromDB() (map[string]entry.Entry, map[string]entry.Entry, error) {
if s.db == nil {
return nil, errors.ErrDBNotInit
return nil, nil, errors.ErrDBNotInit
}

rows, err := s.db.Query("SELECT key, data_type, data, expiry FROM cache_data")
if err != nil {
return nil, err
return nil, nil, err
}
defer rows.Close()

dbData := make(map[string]entry.Entry)
tempdb := make(map[string]entry.Entry)
persidb := make(map[string]entry.Entry)
now := time.Now().UnixMilli()
for rows.Next() {
var key string
Expand All @@ -76,18 +77,25 @@ func (s *SqliteStore) LoadFromDB() (map[string]entry.Entry, error) {
continue
}

if expiry == 0 {
persidb[key] = entry.Entry{
Type: dataType,
Data: data,
}
}

if expiry > 0 && expiry <= now {
continue
}

dbData[key] = entry.Entry{
tempdb[key] = entry.Entry{
Type: dataType,
Data: data,
Expiry: expiry,
}
}

return dbData, nil
return tempdb, persidb, nil
}

func (s *SqliteStore) SaveDirtyData(set_dirtys map[string]entry.Entry, delete_dirtys []string) error {
Expand Down
12 changes: 6 additions & 6 deletions sqlite/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestNewSqliteStore_InvalidFileName(t *testing.T) {

func TestNewSqliteStore_NoDBInit(t *testing.T) {
s := &SqliteStore{}
_, err := s.LoadFromDB()
_, _, err := s.LoadFromDB()
if err == nil {
t.Error("expected error for not initialized sql")
}
Expand Down Expand Up @@ -66,7 +66,7 @@ func TestSqliteStore_Save_ForceLock(t *testing.T) {
store.mux.Unlock()
<-unlocked

loaded, err := store.LoadFromDB()
loaded, _, err := store.LoadFromDB()
if err != nil {
t.Fatalf("load error: %v", err)
}
Expand Down Expand Up @@ -102,7 +102,7 @@ func TestSqliteStore_SaveDirtyData(t *testing.T) {
t.Errorf("force lock Save failed: %v", err)
}

loaded, err := store.LoadFromDB()
loaded, _, err := store.LoadFromDB()
if err != nil {
t.Fatalf("load error: %v", err)
}
Expand All @@ -116,7 +116,7 @@ func TestSqliteStore_SaveDirtyData(t *testing.T) {

store.SaveDirtyData(dirtyData, []string{"foo"})

loaded, err = store.LoadFromDB()
loaded, _, err = store.LoadFromDB()
if err != nil {
t.Fatalf("load error: %v", err)
}
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestSqliteStore_Load(t *testing.T) {
if err != nil {
t.Errorf("force lock Save failed: %v", err)
}
loaded, err := store.LoadFromDB()
loaded, _, err := store.LoadFromDB()
if err != nil {
t.Fatalf("load error: %v", err)
}
Expand All @@ -177,7 +177,7 @@ func TestSqliteStore_Load_FilterExpired(t *testing.T) {
t.Errorf("force lock Save failed: %v", err)
}
time.Sleep(200 * time.Millisecond)
loaded, err := store.LoadFromDB()
loaded, _, err := store.LoadFromDB()
if err != nil {
t.Fatalf("load error: %v", err)
}
Expand Down
Loading