mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-06 00:16:53 +08:00
Implemented a Clock interface that is injected everywhere time.Now and time.After are used.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
logstore "github.com/echovault/echovault/internal/aof/log"
|
logstore "github.com/echovault/echovault/internal/aof/log"
|
||||||
"github.com/echovault/echovault/internal/aof/preamble"
|
"github.com/echovault/echovault/internal/aof/preamble"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -27,6 +28,7 @@ import (
|
|||||||
// Logging in replication clusters is handled in the raft layer.
|
// Logging in replication clusters is handled in the raft layer.
|
||||||
|
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
|
clock clock.Clock
|
||||||
syncStrategy string
|
syncStrategy string
|
||||||
directory string
|
directory string
|
||||||
preambleRW preamble.PreambleReadWriter
|
preambleRW preamble.PreambleReadWriter
|
||||||
@@ -45,6 +47,12 @@ type Engine struct {
|
|||||||
handleCommand func(command []byte)
|
handleCommand func(command []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithClock(clock clock.Clock) func(engine *Engine) {
|
||||||
|
return func(engine *Engine) {
|
||||||
|
engine.clock = clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithStrategy(strategy string) func(engine *Engine) {
|
func WithStrategy(strategy string) func(engine *Engine) {
|
||||||
return func(engine *Engine) {
|
return func(engine *Engine) {
|
||||||
engine.syncStrategy = strategy
|
engine.syncStrategy = strategy
|
||||||
@@ -101,6 +109,7 @@ func WithAppendReadWriter(rw logstore.AppendReadWriter) func(engine *Engine) {
|
|||||||
|
|
||||||
func NewAOFEngine(options ...func(engine *Engine)) *Engine {
|
func NewAOFEngine(options ...func(engine *Engine)) *Engine {
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
|
clock: clock.NewClock(),
|
||||||
syncStrategy: "everysec",
|
syncStrategy: "everysec",
|
||||||
directory: "",
|
directory: "",
|
||||||
mut: sync.Mutex{},
|
mut: sync.Mutex{},
|
||||||
@@ -121,6 +130,7 @@ func NewAOFEngine(options ...func(engine *Engine)) *Engine {
|
|||||||
|
|
||||||
// Setup Preamble engine
|
// Setup Preamble engine
|
||||||
engine.preambleStore = preamble.NewPreambleStore(
|
engine.preambleStore = preamble.NewPreambleStore(
|
||||||
|
preamble.WithClock(engine.clock),
|
||||||
preamble.WithDirectory(engine.directory),
|
preamble.WithDirectory(engine.directory),
|
||||||
preamble.WithReadWriter(engine.preambleRW),
|
preamble.WithReadWriter(engine.preambleRW),
|
||||||
preamble.WithGetStateFunc(engine.getStateFunc),
|
preamble.WithGetStateFunc(engine.getStateFunc),
|
||||||
@@ -129,6 +139,7 @@ func NewAOFEngine(options ...func(engine *Engine)) *Engine {
|
|||||||
|
|
||||||
// Setup AOF log store engine
|
// Setup AOF log store engine
|
||||||
engine.appendStore = logstore.NewAppendStore(
|
engine.appendStore = logstore.NewAppendStore(
|
||||||
|
logstore.WithClock(engine.clock),
|
||||||
logstore.WithDirectory(engine.directory),
|
logstore.WithDirectory(engine.directory),
|
||||||
logstore.WithStrategy(engine.syncStrategy),
|
logstore.WithStrategy(engine.syncStrategy),
|
||||||
logstore.WithReadWriter(engine.appendRW),
|
logstore.WithReadWriter(engine.appendRW),
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -36,6 +37,7 @@ type AppendReadWriter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppendStore struct {
|
type AppendStore struct {
|
||||||
|
clock clock.Clock
|
||||||
strategy string // Append file sync strategy. Can only be "always", "everysec", or "no
|
strategy string // Append file sync strategy. Can only be "always", "everysec", or "no
|
||||||
mut sync.Mutex // Store mutex
|
mut sync.Mutex // Store mutex
|
||||||
rw AppendReadWriter // The ReadWriter used to persist and load the log
|
rw AppendReadWriter // The ReadWriter used to persist and load the log
|
||||||
@@ -43,6 +45,12 @@ type AppendStore struct {
|
|||||||
handleCommand func(command []byte) // Function to handle command read from AOF log after restore
|
handleCommand func(command []byte) // Function to handle command read from AOF log after restore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithClock(clock clock.Clock) func(store *AppendStore) {
|
||||||
|
return func(store *AppendStore) {
|
||||||
|
store.clock = clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithStrategy(strategy string) func(store *AppendStore) {
|
func WithStrategy(strategy string) func(store *AppendStore) {
|
||||||
return func(store *AppendStore) {
|
return func(store *AppendStore) {
|
||||||
store.strategy = strategy
|
store.strategy = strategy
|
||||||
@@ -69,6 +77,7 @@ func WithHandleCommandFunc(f func(command []byte)) func(store *AppendStore) {
|
|||||||
|
|
||||||
func NewAppendStore(options ...func(store *AppendStore)) *AppendStore {
|
func NewAppendStore(options ...func(store *AppendStore)) *AppendStore {
|
||||||
store := &AppendStore{
|
store := &AppendStore{
|
||||||
|
clock: clock.NewClock(),
|
||||||
directory: "",
|
directory: "",
|
||||||
strategy: "everysec",
|
strategy: "everysec",
|
||||||
rw: nil,
|
rw: nil,
|
||||||
@@ -103,7 +112,7 @@ func NewAppendStore(options ...func(store *AppendStore)) *AppendStore {
|
|||||||
log.Println(fmt.Errorf("new append store error: %+v", err))
|
log.Println(fmt.Errorf("new append store error: %+v", err))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
<-time.After(1 * time.Second)
|
<-store.clock.After(1 * time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@@ -18,12 +18,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PreambleReadWriter interface {
|
type PreambleReadWriter interface {
|
||||||
@@ -34,6 +34,7 @@ type PreambleReadWriter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PreambleStore struct {
|
type PreambleStore struct {
|
||||||
|
clock clock.Clock
|
||||||
rw PreambleReadWriter
|
rw PreambleReadWriter
|
||||||
mut sync.Mutex
|
mut sync.Mutex
|
||||||
directory string
|
directory string
|
||||||
@@ -41,6 +42,12 @@ type PreambleStore struct {
|
|||||||
setKeyDataFunc func(key string, data internal.KeyData)
|
setKeyDataFunc func(key string, data internal.KeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithClock(clock clock.Clock) func(store *PreambleStore) {
|
||||||
|
return func(store *PreambleStore) {
|
||||||
|
store.clock = clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithReadWriter(rw PreambleReadWriter) func(store *PreambleStore) {
|
func WithReadWriter(rw PreambleReadWriter) func(store *PreambleStore) {
|
||||||
return func(store *PreambleStore) {
|
return func(store *PreambleStore) {
|
||||||
store.rw = rw
|
store.rw = rw
|
||||||
@@ -67,6 +74,7 @@ func WithDirectory(directory string) func(store *PreambleStore) {
|
|||||||
|
|
||||||
func NewPreambleStore(options ...func(store *PreambleStore)) *PreambleStore {
|
func NewPreambleStore(options ...func(store *PreambleStore)) *PreambleStore {
|
||||||
store := &PreambleStore{
|
store := &PreambleStore{
|
||||||
|
clock: clock.NewClock(),
|
||||||
rw: nil,
|
rw: nil,
|
||||||
mut: sync.Mutex{},
|
mut: sync.Mutex{},
|
||||||
directory: "",
|
directory: "",
|
||||||
@@ -166,7 +174,7 @@ func (store *PreambleStore) Close() error {
|
|||||||
func (store *PreambleStore) filterExpiredKeys(state map[string]internal.KeyData) map[string]internal.KeyData {
|
func (store *PreambleStore) filterExpiredKeys(state map[string]internal.KeyData) map[string]internal.KeyData {
|
||||||
var keysToDelete []string
|
var keysToDelete []string
|
||||||
for k, v := range state {
|
for k, v := range state {
|
||||||
if v.ExpireAt.Before(time.Now()) {
|
if v.ExpireAt.Before(store.clock.Now()) {
|
||||||
keysToDelete = append(keysToDelete, k)
|
keysToDelete = append(keysToDelete, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
internal/clock/clock.go
Normal file
41
internal/clock/clock.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
After(d time.Duration) <-chan time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClock() Clock {
|
||||||
|
// If we're in a test environment, return the mock clock.
|
||||||
|
if strings.Contains(os.Args[0], ".test") {
|
||||||
|
return MockClock{}
|
||||||
|
}
|
||||||
|
return RealClock{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RealClock struct{}
|
||||||
|
|
||||||
|
func (RealClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RealClock) After(d time.Duration) <-chan time.Time {
|
||||||
|
return time.After(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockClock struct{}
|
||||||
|
|
||||||
|
func (MockClock) Now() time.Time {
|
||||||
|
t, _ := time.Parse(time.RFC3339, "2036-01-02T15:04:05+07:00")
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MockClock) After(d time.Duration) <-chan time.Time {
|
||||||
|
return time.After(d)
|
||||||
|
}
|
@@ -20,6 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
@@ -37,6 +38,7 @@ type Manifest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
|
clock clock.Clock
|
||||||
changeCount uint64
|
changeCount uint64
|
||||||
directory string
|
directory string
|
||||||
snapshotInterval time.Duration
|
snapshotInterval time.Duration
|
||||||
@@ -49,6 +51,12 @@ type Engine struct {
|
|||||||
setKeyDataFunc func(key string, data internal.KeyData)
|
setKeyDataFunc func(key string, data internal.KeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithClock(clock clock.Clock) func(engine *Engine) {
|
||||||
|
return func(engine *Engine) {
|
||||||
|
engine.clock = clock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithDirectory(directory string) func(engine *Engine) {
|
func WithDirectory(directory string) func(engine *Engine) {
|
||||||
return func(engine *Engine) {
|
return func(engine *Engine) {
|
||||||
engine.directory = directory
|
engine.directory = directory
|
||||||
@@ -105,6 +113,7 @@ func WithSetKeyDataFunc(f func(key string, data internal.KeyData)) func(engine *
|
|||||||
|
|
||||||
func NewSnapshotEngine(options ...func(engine *Engine)) *Engine {
|
func NewSnapshotEngine(options ...func(engine *Engine)) *Engine {
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
|
clock: clock.NewClock(),
|
||||||
changeCount: 0,
|
changeCount: 0,
|
||||||
directory: "",
|
directory: "",
|
||||||
snapshotInterval: 5 * time.Minute,
|
snapshotInterval: 5 * time.Minute,
|
||||||
@@ -128,7 +137,7 @@ func NewSnapshotEngine(options ...func(engine *Engine)) *Engine {
|
|||||||
if engine.snapshotInterval != 0 {
|
if engine.snapshotInterval != 0 {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-time.After(engine.snapshotInterval)
|
<-engine.clock.After(engine.snapshotInterval)
|
||||||
if engine.changeCount == engine.snapshotThreshold {
|
if engine.changeCount == engine.snapshotThreshold {
|
||||||
if err := engine.TakeSnapshot(); err != nil {
|
if err := engine.TakeSnapshot(); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -146,7 +155,7 @@ func (engine *Engine) TakeSnapshot() error {
|
|||||||
defer engine.finishSnapshotFunc()
|
defer engine.finishSnapshotFunc()
|
||||||
|
|
||||||
// Extract current time
|
// Extract current time
|
||||||
now := time.Now()
|
now := engine.clock.Now()
|
||||||
msec := now.UnixNano() / int64(time.Millisecond)
|
msec := now.UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
// Update manifest file to indicate the latest snapshot.
|
// Update manifest file to indicate the latest snapshot.
|
||||||
|
@@ -16,6 +16,7 @@ package echovault
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"github.com/echovault/echovault/internal/config"
|
"github.com/echovault/echovault/internal/config"
|
||||||
"github.com/echovault/echovault/pkg/commands"
|
"github.com/echovault/echovault/pkg/commands"
|
||||||
"github.com/echovault/echovault/pkg/constants"
|
"github.com/echovault/echovault/pkg/constants"
|
||||||
@@ -26,13 +27,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var timeNow = func() time.Time {
|
|
||||||
now := time.Now()
|
|
||||||
return func() time.Time {
|
|
||||||
return now.Add(5 * time.Hour).Add(30 * time.Minute).Add(30 * time.Second).Add(10 * time.Millisecond)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEchoVault_DEL(t *testing.T) {
|
func TestEchoVault_DEL(t *testing.T) {
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
@@ -81,6 +75,8 @@ func TestEchoVault_DEL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_EXPIRE(t *testing.T) {
|
func TestEchoVault_EXPIRE(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -142,7 +138,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 1000,
|
time: 1000,
|
||||||
expireOpts: EXPIREOptions{NX: true},
|
expireOpts: EXPIREOptions{NX: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key4": {Value: "value4", ExpireAt: timeNow().Add(1000 * time.Second)},
|
"key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -154,7 +150,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 1000,
|
time: 1000,
|
||||||
expireOpts: EXPIREOptions{XX: true},
|
expireOpts: EXPIREOptions{XX: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key5": {Value: "value5", ExpireAt: timeNow().Add(30 * time.Second)},
|
"key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -178,7 +174,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 100000,
|
time: 100000,
|
||||||
expireOpts: EXPIREOptions{GT: true},
|
expireOpts: EXPIREOptions{GT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key7": {Value: "value7", ExpireAt: timeNow().Add(30 * time.Second)},
|
"key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -190,7 +186,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 1000,
|
time: 1000,
|
||||||
expireOpts: EXPIREOptions{GT: true},
|
expireOpts: EXPIREOptions{GT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key8": {Value: "value8", ExpireAt: timeNow().Add(3000 * time.Second)},
|
"key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -214,7 +210,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 1000,
|
time: 1000,
|
||||||
expireOpts: EXPIREOptions{LT: true},
|
expireOpts: EXPIREOptions{LT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key10": {Value: "value10", ExpireAt: timeNow().Add(3000 * time.Second)},
|
"key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -226,7 +222,7 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
time: 50000,
|
time: 50000,
|
||||||
expireOpts: EXPIREOptions{LT: true},
|
expireOpts: EXPIREOptions{LT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key11": {Value: "value11", ExpireAt: timeNow().Add(30 * time.Second)},
|
"key11": {Value: "value11", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -258,6 +254,8 @@ func TestEchoVault_EXPIRE(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_EXPIREAT(t *testing.T) {
|
func TestEchoVault_EXPIREAT(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -281,7 +279,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key1",
|
key: "key1",
|
||||||
expireAtOpts: EXPIREATOptions{},
|
expireAtOpts: EXPIREATOptions{},
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
"key1": {Value: "value1", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
@@ -293,7 +291,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
cmd: "PEXPIREAT",
|
cmd: "PEXPIREAT",
|
||||||
key: "key2",
|
key: "key2",
|
||||||
pexpireAtOpts: PEXPIREATOptions{},
|
pexpireAtOpts: PEXPIREATOptions{},
|
||||||
time: int(timeNow().Add(1000 * time.Second).UnixMilli()),
|
time: int(mockClock.Now().Add(1000 * time.Second).UnixMilli()),
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key2": {Value: "value2", ExpireAt: time.Time{}},
|
"key2": {Value: "value2", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
@@ -304,7 +302,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Set new expire only when key does not have an expiry time with NX flag",
|
name: "Set new expire only when key does not have an expiry time with NX flag",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key3",
|
key: "key3",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{NX: true},
|
expireAtOpts: EXPIREATOptions{NX: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
"key3": {Value: "value3", ExpireAt: time.Time{}},
|
||||||
@@ -315,11 +313,11 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Return 0, when NX flag is provided and key already has an expiry time",
|
name: "Return 0, when NX flag is provided and key already has an expiry time",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{NX: true},
|
expireAtOpts: EXPIREATOptions{NX: true},
|
||||||
key: "key4",
|
key: "key4",
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key4": {Value: "value4", ExpireAt: timeNow().Add(1000 * time.Second)},
|
"key4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -327,11 +325,11 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Set new expire time from now key only when the key already has an expiry time with XX flag",
|
name: "Set new expire time from now key only when the key already has an expiry time with XX flag",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
key: "key5",
|
key: "key5",
|
||||||
expireAtOpts: EXPIREATOptions{XX: true},
|
expireAtOpts: EXPIREATOptions{XX: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key5": {Value: "value5", ExpireAt: timeNow().Add(30 * time.Second)},
|
"key5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -340,7 +338,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Return 0 when key does not have an expiry and the XX flag is provided",
|
name: "Return 0 when key does not have an expiry and the XX flag is provided",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key6",
|
key: "key6",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{XX: true},
|
expireAtOpts: EXPIREATOptions{XX: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key6": {Value: "value6", ExpireAt: time.Time{}},
|
"key6": {Value: "value6", ExpireAt: time.Time{}},
|
||||||
@@ -352,10 +350,10 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided",
|
name: "Set expiry time when the provided time is after the current expiry time when GT flag is provided",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key7",
|
key: "key7",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{GT: true},
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key7": {Value: "value7", ExpireAt: timeNow().Add(30 * time.Second)},
|
"key7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -364,10 +362,10 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Return 0 when GT flag is passed and current expiry time is greater than provided time",
|
name: "Return 0 when GT flag is passed and current expiry time is greater than provided time",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key8",
|
key: "key8",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{GT: true},
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key8": {Value: "value8", ExpireAt: timeNow().Add(3000 * time.Second)},
|
"key8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -376,7 +374,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Return 0 when GT flag is passed and key does not have an expiry time",
|
name: "Return 0 when GT flag is passed and key does not have an expiry time",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key9",
|
key: "key9",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{GT: true},
|
expireAtOpts: EXPIREATOptions{GT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key9": {Value: "value9", ExpireAt: time.Time{}},
|
"key9": {Value: "value9", ExpireAt: time.Time{}},
|
||||||
@@ -387,10 +385,10 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided",
|
name: "Set expiry time when the provided time is before the current expiry time when LT flag is provided",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key10",
|
key: "key10",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{LT: true},
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key10": {Value: "value10", ExpireAt: timeNow().Add(3000 * time.Second)},
|
"key10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 1,
|
want: 1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -399,10 +397,10 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Return 0 when LT flag is passed and current expiry time is less than provided time",
|
name: "Return 0 when LT flag is passed and current expiry time is less than provided time",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key11",
|
key: "key11",
|
||||||
time: int(timeNow().Add(3000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(3000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{LT: true},
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key11": {Value: "value11", ExpireAt: timeNow().Add(1000 * time.Second)},
|
"key11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: 0,
|
want: 0,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -411,7 +409,7 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
name: "Return 0 when LT flag is passed and key does not have an expiry time",
|
name: "Return 0 when LT flag is passed and key does not have an expiry time",
|
||||||
cmd: "EXPIREAT",
|
cmd: "EXPIREAT",
|
||||||
key: "key12",
|
key: "key12",
|
||||||
time: int(timeNow().Add(1000 * time.Second).Unix()),
|
time: int(mockClock.Now().Add(1000 * time.Second).Unix()),
|
||||||
expireAtOpts: EXPIREATOptions{LT: true},
|
expireAtOpts: EXPIREATOptions{LT: true},
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key12": {Value: "value12", ExpireAt: time.Time{}},
|
"key12": {Value: "value12", ExpireAt: time.Time{}},
|
||||||
@@ -446,6 +444,8 @@ func TestEchoVault_EXPIREAT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_EXPIRETIME(t *testing.T) {
|
func TestEchoVault_EXPIRETIME(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -465,20 +465,20 @@ func TestEchoVault_EXPIRETIME(t *testing.T) {
|
|||||||
name: "Return expire time in seconds",
|
name: "Return expire time in seconds",
|
||||||
key: "key1",
|
key: "key1",
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key1": {Value: "value1", ExpireAt: timeNow().Add(100 * time.Second)},
|
"key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
||||||
},
|
},
|
||||||
expiretimeFunc: server.EXPIRETIME,
|
expiretimeFunc: server.EXPIRETIME,
|
||||||
want: int(timeNow().Add(100 * time.Second).Unix()),
|
want: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Return expire time in milliseconds",
|
name: "Return expire time in milliseconds",
|
||||||
key: "key2",
|
key: "key2",
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key2": {Value: "value2", ExpireAt: timeNow().Add(4096 * time.Millisecond)},
|
"key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
||||||
},
|
},
|
||||||
expiretimeFunc: server.PEXPIRETIME,
|
expiretimeFunc: server.PEXPIRETIME,
|
||||||
want: int(timeNow().Add(4096 * time.Millisecond).UnixMilli()),
|
want: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli()),
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -623,6 +623,8 @@ func TestEchoVault_MGET(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_SET(t *testing.T) {
|
func TestEchoVault_SET(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -717,7 +719,7 @@ func TestEchoVault_SET(t *testing.T) {
|
|||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
key: "key8",
|
key: "key8",
|
||||||
value: "value8",
|
value: "value8",
|
||||||
options: SETOptions{EXAT: int(timeNow().Add(200 * time.Second).Unix())},
|
options: SETOptions{EXAT: int(mockClock.Now().Add(200 * time.Second).Unix())},
|
||||||
want: "OK",
|
want: "OK",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -725,7 +727,7 @@ func TestEchoVault_SET(t *testing.T) {
|
|||||||
name: "Set exact expiry time in milliseconds from unix epoch",
|
name: "Set exact expiry time in milliseconds from unix epoch",
|
||||||
key: "key9",
|
key: "key9",
|
||||||
value: "value9",
|
value: "value9",
|
||||||
options: SETOptions{PXAT: int(timeNow().Add(4096 * time.Millisecond).UnixMilli())},
|
options: SETOptions{PXAT: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli())},
|
||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
want: "OK",
|
want: "OK",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -809,6 +811,8 @@ func TestEchoVault_MSET(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_PERSIST(t *testing.T) {
|
func TestEchoVault_PERSIST(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -827,7 +831,7 @@ func TestEchoVault_PERSIST(t *testing.T) {
|
|||||||
name: "Successfully persist a volatile key",
|
name: "Successfully persist a volatile key",
|
||||||
key: "key1",
|
key: "key1",
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key1": {Value: "value1", ExpireAt: timeNow().Add(1000 * time.Second)},
|
"key1": {Value: "value1", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -869,6 +873,8 @@ func TestEchoVault_PERSIST(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEchoVault_TTL(t *testing.T) {
|
func TestEchoVault_TTL(t *testing.T) {
|
||||||
|
mockClock := clock.NewClock()
|
||||||
|
|
||||||
server, _ := NewEchoVault(
|
server, _ := NewEchoVault(
|
||||||
WithCommands(commands.All()),
|
WithCommands(commands.All()),
|
||||||
WithConfig(config.Config{
|
WithConfig(config.Config{
|
||||||
@@ -888,10 +894,10 @@ func TestEchoVault_TTL(t *testing.T) {
|
|||||||
name: "Return TTL time in seconds",
|
name: "Return TTL time in seconds",
|
||||||
key: "key1",
|
key: "key1",
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key1": {Value: "value1", ExpireAt: timeNow().Add(100 * time.Second)},
|
"key1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
||||||
},
|
},
|
||||||
ttlFunc: server.TTL,
|
ttlFunc: server.TTL,
|
||||||
want: 19930,
|
want: 100,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -899,9 +905,9 @@ func TestEchoVault_TTL(t *testing.T) {
|
|||||||
key: "key2",
|
key: "key2",
|
||||||
ttlFunc: server.PTTL,
|
ttlFunc: server.PTTL,
|
||||||
presetValues: map[string]internal.KeyData{
|
presetValues: map[string]internal.KeyData{
|
||||||
"key2": {Value: "value2", ExpireAt: timeNow().Add(4096 * time.Millisecond)},
|
"key2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
||||||
},
|
},
|
||||||
want: 19834106,
|
want: 4096,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/echovault/echovault/internal"
|
"github.com/echovault/echovault/internal"
|
||||||
"github.com/echovault/echovault/internal/acl"
|
"github.com/echovault/echovault/internal/acl"
|
||||||
"github.com/echovault/echovault/internal/aof"
|
"github.com/echovault/echovault/internal/aof"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"github.com/echovault/echovault/internal/config"
|
"github.com/echovault/echovault/internal/config"
|
||||||
"github.com/echovault/echovault/internal/eviction"
|
"github.com/echovault/echovault/internal/eviction"
|
||||||
"github.com/echovault/echovault/internal/memberlist"
|
"github.com/echovault/echovault/internal/memberlist"
|
||||||
@@ -41,6 +42,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EchoVault struct {
|
type EchoVault struct {
|
||||||
|
// clock is an implementation of a time interface that allows mocking of time functions during testing.
|
||||||
|
clock clock.Clock
|
||||||
|
|
||||||
// config holds the echovault configuration variables.
|
// config holds the echovault configuration variables.
|
||||||
config config.Config
|
config config.Config
|
||||||
|
|
||||||
@@ -90,8 +94,8 @@ type EchoVault struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithContext is an options that for the NewEchoVault function that allows you to
|
// WithContext is an options that for the NewEchoVault function that allows you to
|
||||||
// configure a custom context object to be used in EchoVault. If you don't provide this
|
// configure a custom context object to be used in EchoVault.
|
||||||
// option, EchoVault will create its own internal context object.
|
// If you don't provide this option, EchoVault will create its own internal context object.
|
||||||
func WithContext(ctx context.Context) func(echovault *EchoVault) {
|
func WithContext(ctx context.Context) func(echovault *EchoVault) {
|
||||||
return func(echovault *EchoVault) {
|
return func(echovault *EchoVault) {
|
||||||
echovault.context = ctx
|
echovault.context = ctx
|
||||||
@@ -99,8 +103,8 @@ func WithContext(ctx context.Context) func(echovault *EchoVault) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithConfig is an option for the NewEchoVault function that allows you to pass a
|
// WithConfig is an option for the NewEchoVault function that allows you to pass a
|
||||||
// custom configuration to EchoVault. If not specified, EchoVault will use the default
|
// custom configuration to EchoVault.
|
||||||
// configuration from config.DefaultConfig().
|
// If not specified, EchoVault will use the default configuration from config.DefaultConfig().
|
||||||
func WithConfig(config config.Config) func(echovault *EchoVault) {
|
func WithConfig(config config.Config) func(echovault *EchoVault) {
|
||||||
return func(echovault *EchoVault) {
|
return func(echovault *EchoVault) {
|
||||||
echovault.config = config
|
echovault.config = config
|
||||||
@@ -108,8 +112,8 @@ func WithConfig(config config.Config) func(echovault *EchoVault) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithCommands is an options for the NewEchoVault function that allows you to pass a
|
// WithCommands is an options for the NewEchoVault function that allows you to pass a
|
||||||
// list of commands that should be supported by your EchoVault instance. If you don't pass
|
// list of commands that should be supported by your EchoVault instance.
|
||||||
// this option, EchoVault will start with no commands loaded.
|
// If you don't pass this option, EchoVault will start with no commands loaded.
|
||||||
func WithCommands(commands []types.Command) func(echovault *EchoVault) {
|
func WithCommands(commands []types.Command) func(echovault *EchoVault) {
|
||||||
return func(echovault *EchoVault) {
|
return func(echovault *EchoVault) {
|
||||||
echovault.commands = commands
|
echovault.commands = commands
|
||||||
@@ -120,6 +124,7 @@ func WithCommands(commands []types.Command) func(echovault *EchoVault) {
|
|||||||
// This functions accepts the WithContext, WithConfig and WithCommands options.
|
// This functions accepts the WithContext, WithConfig and WithCommands options.
|
||||||
func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
||||||
echovault := &EchoVault{
|
echovault := &EchoVault{
|
||||||
|
clock: clock.NewClock(),
|
||||||
context: context.Background(),
|
context: context.Background(),
|
||||||
commands: make([]types.Command, 0),
|
commands: make([]types.Command, 0),
|
||||||
config: config.DefaultConfig(),
|
config: config.DefaultConfig(),
|
||||||
@@ -174,6 +179,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
} else {
|
} else {
|
||||||
// Set up standalone snapshot engine
|
// Set up standalone snapshot engine
|
||||||
echovault.snapshotEngine = snapshot.NewSnapshotEngine(
|
echovault.snapshotEngine = snapshot.NewSnapshotEngine(
|
||||||
|
snapshot.WithClock(echovault.clock),
|
||||||
snapshot.WithDirectory(echovault.config.DataDir),
|
snapshot.WithDirectory(echovault.config.DataDir),
|
||||||
snapshot.WithThreshold(echovault.config.SnapShotThreshold),
|
snapshot.WithThreshold(echovault.config.SnapShotThreshold),
|
||||||
snapshot.WithInterval(echovault.config.SnapshotInterval),
|
snapshot.WithInterval(echovault.config.SnapshotInterval),
|
||||||
@@ -204,6 +210,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
)
|
)
|
||||||
// Set up standalone AOF engine
|
// Set up standalone AOF engine
|
||||||
echovault.aofEngine = aof.NewAOFEngine(
|
echovault.aofEngine = aof.NewAOFEngine(
|
||||||
|
aof.WithClock(echovault.clock),
|
||||||
aof.WithDirectory(echovault.config.DataDir),
|
aof.WithDirectory(echovault.config.DataDir),
|
||||||
aof.WithStrategy(echovault.config.AOFSyncStrategy),
|
aof.WithStrategy(echovault.config.AOFSyncStrategy),
|
||||||
aof.WithStartRewriteFunc(echovault.startRewriteAOF),
|
aof.WithStartRewriteFunc(echovault.startRewriteAOF),
|
||||||
@@ -241,7 +248,7 @@ func NewEchoVault(options ...func(echovault *EchoVault)) (*EchoVault, error) {
|
|||||||
if echovault.config.EvictionPolicy != constants.NoEviction {
|
if echovault.config.EvictionPolicy != constants.NoEviction {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-time.After(echovault.config.EvictionInterval)
|
<-echovault.clock.After(echovault.config.EvictionInterval)
|
||||||
if err := echovault.evictKeysWithExpiredTTL(context.Background()); err != nil {
|
if err := echovault.evictKeysWithExpiredTTL(context.Background()); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
@@ -465,6 +472,11 @@ func (server *EchoVault) TakeSnapshot() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClock returns the server's clock implementation
|
||||||
|
func (server *EchoVault) GetClock() clock.Clock {
|
||||||
|
return server.clock
|
||||||
|
}
|
||||||
|
|
||||||
func (server *EchoVault) startSnapshot() {
|
func (server *EchoVault) startSnapshot() {
|
||||||
server.snapshotInProgress.Store(true)
|
server.snapshotInProgress.Store(true)
|
||||||
}
|
}
|
||||||
|
@@ -118,7 +118,7 @@ func (server *EchoVault) KeyExists(ctx context.Context, key string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.ExpireAt != (time.Time{}) && entry.ExpireAt.Before(time.Now()) {
|
if entry.ExpireAt != (time.Time{}) && entry.ExpireAt.Before(server.clock.Now()) {
|
||||||
if !server.isInCluster() {
|
if !server.isInCluster() {
|
||||||
// If in standalone mode, delete the key directly.
|
// If in standalone mode, delete the key directly.
|
||||||
err := server.DeleteKey(ctx, key)
|
err := server.DeleteKey(ctx, key)
|
||||||
@@ -553,7 +553,7 @@ func (server *EchoVault) evictKeysWithExpiredTTL(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the current key is not expired, skip to the next key
|
// If the current key is not expired, skip to the next key
|
||||||
if server.store[k].ExpireAt.After(time.Now()) {
|
if server.store[k].ExpireAt.After(server.clock.Now()) {
|
||||||
server.KeyRUnlock(ctx, k)
|
server.KeyRUnlock(ctx, k)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -42,8 +42,9 @@ func handleSet(ctx context.Context, cmd []string, server types.EchoVault, _ *net
|
|||||||
key := keys[0]
|
key := keys[0]
|
||||||
value := cmd[2]
|
value := cmd[2]
|
||||||
res := []byte(constants.OkResponse)
|
res := []byte(constants.OkResponse)
|
||||||
|
clock := server.GetClock()
|
||||||
|
|
||||||
params, err := getSetCommandParams(cmd[3:], SetParams{})
|
params, err := getSetCommandParams(clock, cmd[3:], SetParams{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -308,6 +309,8 @@ func handleTTL(ctx context.Context, cmd []string, server types.EchoVault, _ *net
|
|||||||
|
|
||||||
key := keys[0]
|
key := keys[0]
|
||||||
|
|
||||||
|
clock := server.GetClock()
|
||||||
|
|
||||||
if !server.KeyExists(ctx, key) {
|
if !server.KeyExists(ctx, key) {
|
||||||
return []byte(":-2\r\n"), nil
|
return []byte(":-2\r\n"), nil
|
||||||
}
|
}
|
||||||
@@ -323,9 +326,9 @@ func handleTTL(ctx context.Context, cmd []string, server types.EchoVault, _ *net
|
|||||||
return []byte(":-1\r\n"), nil
|
return []byte(":-1\r\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t := expireAt.Unix() - time.Now().Unix()
|
t := expireAt.Unix() - clock.Now().Unix()
|
||||||
if strings.ToLower(cmd[0]) == "pttl" {
|
if strings.ToLower(cmd[0]) == "pttl" {
|
||||||
t = expireAt.UnixMilli() - time.Now().UnixMilli()
|
t = expireAt.UnixMilli() - clock.Now().UnixMilli()
|
||||||
}
|
}
|
||||||
|
|
||||||
if t <= 0 {
|
if t <= 0 {
|
||||||
@@ -348,9 +351,9 @@ func handleExpire(ctx context.Context, cmd []string, server types.EchoVault, _ *
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("expire time must be integer")
|
return nil, errors.New("expire time must be integer")
|
||||||
}
|
}
|
||||||
expireAt := time.Now().Add(time.Duration(n) * time.Second)
|
expireAt := server.GetClock().Now().Add(time.Duration(n) * time.Second)
|
||||||
if strings.ToLower(cmd[0]) == "pexpire" {
|
if strings.ToLower(cmd[0]) == "pexpire" {
|
||||||
expireAt = time.Now().Add(time.Duration(n) * time.Millisecond)
|
expireAt = server.GetClock().Now().Add(time.Duration(n) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !server.KeyExists(ctx, key) {
|
if !server.KeyExists(ctx, key) {
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"github.com/echovault/echovault/internal/config"
|
"github.com/echovault/echovault/internal/config"
|
||||||
"github.com/echovault/echovault/pkg/constants"
|
"github.com/echovault/echovault/pkg/constants"
|
||||||
"github.com/echovault/echovault/pkg/echovault"
|
"github.com/echovault/echovault/pkg/echovault"
|
||||||
@@ -29,12 +30,16 @@ import (
|
|||||||
|
|
||||||
var mockServer *echovault.EchoVault
|
var mockServer *echovault.EchoVault
|
||||||
|
|
||||||
|
var mockClock clock.Clock
|
||||||
|
|
||||||
type KeyData struct {
|
type KeyData struct {
|
||||||
Value interface{}
|
Value interface{}
|
||||||
ExpireAt time.Time
|
ExpireAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
mockClock = clock.NewClock()
|
||||||
|
|
||||||
mockServer, _ = echovault.NewEchoVault(
|
mockServer, _ = echovault.NewEchoVault(
|
||||||
echovault.WithConfig(config.Config{
|
echovault.WithConfig(config.Config{
|
||||||
DataDir: "",
|
DataDir: "",
|
||||||
@@ -139,7 +144,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: "OK",
|
expectedResponse: "OK",
|
||||||
expectedValue: "value10",
|
expectedValue: "value10",
|
||||||
expectedExpiry: time.Now().Add(100 * time.Second),
|
expectedExpiry: mockClock.Now().Add(100 * time.Second),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 11. Return error when EX flag is passed without seconds value
|
{ // 11. Return error when EX flag is passed without seconds value
|
||||||
@@ -171,7 +176,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: "OK",
|
expectedResponse: "OK",
|
||||||
expectedValue: "value14",
|
expectedValue: "value14",
|
||||||
expectedExpiry: time.Now().Add(4096 * time.Millisecond),
|
expectedExpiry: mockClock.Now().Add(4096 * time.Millisecond),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 15. Return error when PX flag is passed without milliseconds value
|
{ // 15. Return error when PX flag is passed without milliseconds value
|
||||||
@@ -201,19 +206,19 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
{ // 18. Set exact expiry time in seconds from unix epoch
|
{ // 18. Set exact expiry time in seconds from unix epoch
|
||||||
command: []string{
|
command: []string{
|
||||||
"SET", "SetKey18", "value18",
|
"SET", "SetKey18", "value18",
|
||||||
"EXAT", fmt.Sprintf("%d", time.Now().Add(200*time.Second).Unix()),
|
"EXAT", fmt.Sprintf("%d", mockClock.Now().Add(200*time.Second).Unix()),
|
||||||
},
|
},
|
||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: "OK",
|
expectedResponse: "OK",
|
||||||
expectedValue: "value18",
|
expectedValue: "value18",
|
||||||
expectedExpiry: time.Now().Add(200 * time.Second),
|
expectedExpiry: mockClock.Now().Add(200 * time.Second),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 19. Return error when trying to set exact seconds expiry time when expiry time is already provided
|
{ // 19. Return error when trying to set exact seconds expiry time when expiry time is already provided
|
||||||
command: []string{
|
command: []string{
|
||||||
"SET", "SetKey19", "value19",
|
"SET", "SetKey19", "value19",
|
||||||
"EX", "10",
|
"EX", "10",
|
||||||
"EXAT", fmt.Sprintf("%d", time.Now().Add(200*time.Second).Unix()),
|
"EXAT", fmt.Sprintf("%d", mockClock.Now().Add(200*time.Second).Unix()),
|
||||||
},
|
},
|
||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: nil,
|
expectedResponse: nil,
|
||||||
@@ -240,19 +245,19 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
{ // 22. Set exact expiry time in milliseconds from unix epoch
|
{ // 22. Set exact expiry time in milliseconds from unix epoch
|
||||||
command: []string{
|
command: []string{
|
||||||
"SET", "SetKey22", "value22",
|
"SET", "SetKey22", "value22",
|
||||||
"PXAT", fmt.Sprintf("%d", time.Now().Add(4096*time.Millisecond).UnixMilli()),
|
"PXAT", fmt.Sprintf("%d", mockClock.Now().Add(4096*time.Millisecond).UnixMilli()),
|
||||||
},
|
},
|
||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: "OK",
|
expectedResponse: "OK",
|
||||||
expectedValue: "value22",
|
expectedValue: "value22",
|
||||||
expectedExpiry: time.Now().Add(4096 * time.Millisecond),
|
expectedExpiry: mockClock.Now().Add(4096 * time.Millisecond),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 23. Return error when trying to set exact milliseconds expiry time when expiry time is already provided
|
{ // 23. Return error when trying to set exact milliseconds expiry time when expiry time is already provided
|
||||||
command: []string{
|
command: []string{
|
||||||
"SET", "SetKey23", "value23",
|
"SET", "SetKey23", "value23",
|
||||||
"PX", "1000",
|
"PX", "1000",
|
||||||
"PXAT", fmt.Sprintf("%d", time.Now().Add(4096*time.Millisecond).UnixMilli()),
|
"PXAT", fmt.Sprintf("%d", mockClock.Now().Add(4096*time.Millisecond).UnixMilli()),
|
||||||
},
|
},
|
||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: nil,
|
expectedResponse: nil,
|
||||||
@@ -286,7 +291,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedResponse: "previous-value",
|
expectedResponse: "previous-value",
|
||||||
expectedValue: "value26",
|
expectedValue: "value26",
|
||||||
expectedExpiry: time.Now().Add(1000 * time.Second),
|
expectedExpiry: mockClock.Now().Add(1000 * time.Second),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 27. Return nil when GET value is passed and no previous value exists
|
{ // 27. Return nil when GET value is passed and no previous value exists
|
||||||
@@ -294,7 +299,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
presetValues: nil,
|
presetValues: nil,
|
||||||
expectedResponse: nil,
|
expectedResponse: nil,
|
||||||
expectedValue: "value27",
|
expectedValue: "value27",
|
||||||
expectedExpiry: time.Now().Add(1000 * time.Second),
|
expectedExpiry: mockClock.Now().Add(1000 * time.Second),
|
||||||
expectedErr: nil,
|
expectedErr: nil,
|
||||||
},
|
},
|
||||||
{ // 28. Throw error when unknown optional flag is passed to SET command.
|
{ // 28. Throw error when unknown optional flag is passed to SET command.
|
||||||
@@ -320,7 +325,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("SET, %d", i))
|
ctx := context.WithValue(context.Background(), "test_name", fmt.Sprintf("SET, %d", i+1))
|
||||||
|
|
||||||
if test.presetValues != nil {
|
if test.presetValues != nil {
|
||||||
for k, v := range test.presetValues {
|
for k, v := range test.presetValues {
|
||||||
@@ -384,7 +389,7 @@ func Test_HandleSET(t *testing.T) {
|
|||||||
t.Errorf("expected value %+v, got %+v", test.expectedValue, value)
|
t.Errorf("expected value %+v, got %+v", test.expectedValue, value)
|
||||||
}
|
}
|
||||||
if test.expectedExpiry.Unix() != expireAt.Unix() {
|
if test.expectedExpiry.Unix() != expireAt.Unix() {
|
||||||
t.Errorf("expected expiry time %d, got %d", test.expectedExpiry.Unix(), expireAt.Unix())
|
t.Errorf("expected expiry time %d, got %d, cmd: %+v", test.expectedExpiry.Unix(), expireAt.Unix(), test.command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -714,7 +719,7 @@ func Test_HandlePERSIST(t *testing.T) {
|
|||||||
{ // 1. Successfully persist a volatile key
|
{ // 1. Successfully persist a volatile key
|
||||||
command: []string{"PERSIST", "PersistKey1"},
|
command: []string{"PERSIST", "PersistKey1"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"PersistKey1": {Value: "value1", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"PersistKey1": {Value: "value1", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
@@ -827,17 +832,17 @@ func Test_HandleEXPIRETIME(t *testing.T) {
|
|||||||
{ // 1. Return expire time in seconds
|
{ // 1. Return expire time in seconds
|
||||||
command: []string{"EXPIRETIME", "ExpireTimeKey1"},
|
command: []string{"EXPIRETIME", "ExpireTimeKey1"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireTimeKey1": {Value: "value1", ExpireAt: time.Now().Add(100 * time.Second)},
|
"ExpireTimeKey1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: int(time.Now().Add(100 * time.Second).Unix()),
|
expectedResponse: int(mockClock.Now().Add(100 * time.Second).Unix()),
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 2. Return expire time in milliseconds
|
{ // 2. Return expire time in milliseconds
|
||||||
command: []string{"PEXPIRETIME", "ExpireTimeKey2"},
|
command: []string{"PEXPIRETIME", "ExpireTimeKey2"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireTimeKey2": {Value: "value2", ExpireAt: time.Now().Add(4096 * time.Millisecond)},
|
"ExpireTimeKey2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
||||||
},
|
},
|
||||||
expectedResponse: int(time.Now().Add(4096 * time.Millisecond).UnixMilli()),
|
expectedResponse: int(mockClock.Now().Add(4096 * time.Millisecond).UnixMilli()),
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 3. If the key is non-volatile, return -1
|
{ // 3. If the key is non-volatile, return -1
|
||||||
@@ -920,7 +925,7 @@ func Test_HandleTTL(t *testing.T) {
|
|||||||
{ // 1. Return TTL time in seconds
|
{ // 1. Return TTL time in seconds
|
||||||
command: []string{"TTL", "TTLKey1"},
|
command: []string{"TTL", "TTLKey1"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"TTLKey1": {Value: "value1", ExpireAt: time.Now().Add(100 * time.Second)},
|
"TTLKey1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 100,
|
expectedResponse: 100,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -928,7 +933,7 @@ func Test_HandleTTL(t *testing.T) {
|
|||||||
{ // 2. Return TTL time in milliseconds
|
{ // 2. Return TTL time in milliseconds
|
||||||
command: []string{"PTTL", "TTLKey2"},
|
command: []string{"PTTL", "TTLKey2"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"TTLKey2": {Value: "value2", ExpireAt: time.Now().Add(4096 * time.Millisecond)},
|
"TTLKey2": {Value: "value2", ExpireAt: mockClock.Now().Add(4096 * time.Millisecond)},
|
||||||
},
|
},
|
||||||
expectedResponse: 4096,
|
expectedResponse: 4096,
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
@@ -1018,7 +1023,7 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey1": {Value: "value1", ExpireAt: time.Now().Add(100 * time.Second)},
|
"ExpireKey1": {Value: "value1", ExpireAt: mockClock.Now().Add(100 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1029,7 +1034,7 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey2": {Value: "value2", ExpireAt: time.Now().Add(1000 * time.Millisecond)},
|
"ExpireKey2": {Value: "value2", ExpireAt: mockClock.Now().Add(1000 * time.Millisecond)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1040,29 +1045,29 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey3": {Value: "value3", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey3": {Value: "value3", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 4. Return 0, when NX flag is provided and key already has an expiry time
|
{ // 4. Return 0, when NX flag is provided and key already has an expiry time
|
||||||
command: []string{"EXPIRE", "ExpireKey4", "1000", "NX"},
|
command: []string{"EXPIRE", "ExpireKey4", "1000", "NX"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey4": {Value: "value4", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey4": {Value: "value4", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 5. Set new expire time from now key only when the key already has an expiry time with XX flag
|
{ // 5. Set new expire time from now key only when the key already has an expiry time with XX flag
|
||||||
command: []string{"EXPIRE", "ExpireKey5", "1000", "XX"},
|
command: []string{"EXPIRE", "ExpireKey5", "1000", "XX"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey5": {Value: "value5", ExpireAt: time.Now().Add(30 * time.Second)},
|
"ExpireKey5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey5": {Value: "value5", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey5": {Value: "value5", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1080,22 +1085,22 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
{ // 7. Set expiry time when the provided time is after the current expiry time when GT flag is provided
|
{ // 7. Set expiry time when the provided time is after the current expiry time when GT flag is provided
|
||||||
command: []string{"EXPIRE", "ExpireKey7", "1000", "GT"},
|
command: []string{"EXPIRE", "ExpireKey7", "1000", "GT"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey7": {Value: "value7", ExpireAt: time.Now().Add(30 * time.Second)},
|
"ExpireKey7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey7": {Value: "value7", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey7": {Value: "value7", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 8. Return 0 when GT flag is passed and current expiry time is greater than provided time
|
{ // 8. Return 0 when GT flag is passed and current expiry time is greater than provided time
|
||||||
command: []string{"EXPIRE", "ExpireKey8", "1000", "GT"},
|
command: []string{"EXPIRE", "ExpireKey8", "1000", "GT"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey8": {Value: "value8", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireKey8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey8": {Value: "value8", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireKey8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1113,22 +1118,22 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
{ // 10. Set expiry time when the provided time is before the current expiry time when LT flag is provided
|
{ // 10. Set expiry time when the provided time is before the current expiry time when LT flag is provided
|
||||||
command: []string{"EXPIRE", "ExpireKey10", "1000", "LT"},
|
command: []string{"EXPIRE", "ExpireKey10", "1000", "LT"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey10": {Value: "value10", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireKey10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey10": {Value: "value10", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey10": {Value: "value10", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 11. Return 0 when LT flag is passed and current expiry time is less than provided time
|
{ // 11. Return 0 when LT flag is passed and current expiry time is less than provided time
|
||||||
command: []string{"EXPIRE", "ExpireKey11", "5000", "LT"},
|
command: []string{"EXPIRE", "ExpireKey11", "5000", "LT"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireKey11": {Value: "value11", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireKey11": {Value: "value11", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey11": {Value: "value11", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireKey11": {Value: "value11", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1139,7 +1144,7 @@ func Test_HandleEXPIRE(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireKey12": {Value: "value12", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireKey12": {Value: "value12", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
@@ -1245,67 +1250,67 @@ func Test_HandleEXPIREAT(t *testing.T) {
|
|||||||
expectedError error
|
expectedError error
|
||||||
}{
|
}{
|
||||||
{ // 1. Set new expire by unix seconds
|
{ // 1. Set new expire by unix seconds
|
||||||
command: []string{"EXPIREAT", "ExpireAtKey1", fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix())},
|
command: []string{"EXPIREAT", "ExpireAtKey1", fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix())},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey1": {Value: "value1", ExpireAt: time.Time{}},
|
"ExpireAtKey1": {Value: "value1", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey1": {Value: "value1", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey1": {Value: "value1", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 2. Set new expire by milliseconds
|
{ // 2. Set new expire by milliseconds
|
||||||
command: []string{"PEXPIREAT", "ExpireAtKey2", fmt.Sprintf("%d", time.Now().Add(1000*time.Second).UnixMilli())},
|
command: []string{"PEXPIREAT", "ExpireAtKey2", fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).UnixMilli())},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey2": {Value: "value2", ExpireAt: time.Time{}},
|
"ExpireAtKey2": {Value: "value2", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey2": {Value: "value2", ExpireAt: time.UnixMilli(time.Now().Add(1000 * time.Second).UnixMilli())},
|
"ExpireAtKey2": {Value: "value2", ExpireAt: time.UnixMilli(mockClock.Now().Add(1000 * time.Second).UnixMilli())},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 3. Set new expire only when key does not have an expiry time with NX flag
|
{ // 3. Set new expire only when key does not have an expiry time with NX flag
|
||||||
command: []string{"EXPIREAT", "ExpireAtKey3", fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "NX"},
|
command: []string{"EXPIREAT", "ExpireAtKey3", fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "NX"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey3": {Value: "value3", ExpireAt: time.Time{}},
|
"ExpireAtKey3": {Value: "value3", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey3": {Value: "value3", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey3": {Value: "value3", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 4. Return 0, when NX flag is provided and key already has an expiry time
|
{ // 4. Return 0, when NX flag is provided and key already has an expiry time
|
||||||
command: []string{"EXPIREAT", "ExpireAtKey4", fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "NX"},
|
command: []string{"EXPIREAT", "ExpireAtKey4", fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "NX"},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey4": {Value: "value4", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireAtKey4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey4": {Value: "value4", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireAtKey4": {Value: "value4", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 5. Set new expire time from now key only when the key already has an expiry time with XX flag
|
{ // 5. Set new expire time from now key only when the key already has an expiry time with XX flag
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey5",
|
"EXPIREAT", "ExpireAtKey5",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "XX",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "XX",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey5": {Value: "value5", ExpireAt: time.Now().Add(30 * time.Second)},
|
"ExpireAtKey5": {Value: "value5", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey5": {Value: "value5", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey5": {Value: "value5", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 6. Return 0 when key does not have an expiry and the XX flag is provided
|
{ // 6. Return 0 when key does not have an expiry and the XX flag is provided
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey6",
|
"EXPIREAT", "ExpireAtKey6",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "XX",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "XX",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey6": {Value: "value6", ExpireAt: time.Time{}},
|
"ExpireAtKey6": {Value: "value6", ExpireAt: time.Time{}},
|
||||||
@@ -1319,35 +1324,35 @@ func Test_HandleEXPIREAT(t *testing.T) {
|
|||||||
{ // 7. Set expiry time when the provided time is after the current expiry time when GT flag is provided
|
{ // 7. Set expiry time when the provided time is after the current expiry time when GT flag is provided
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey7",
|
"EXPIREAT", "ExpireAtKey7",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "GT",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "GT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey7": {Value: "value7", ExpireAt: time.Now().Add(30 * time.Second)},
|
"ExpireAtKey7": {Value: "value7", ExpireAt: mockClock.Now().Add(30 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey7": {Value: "value7", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey7": {Value: "value7", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 8. Return 0 when GT flag is passed and current expiry time is greater than provided time
|
{ // 8. Return 0 when GT flag is passed and current expiry time is greater than provided time
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey8",
|
"EXPIREAT", "ExpireAtKey8",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "GT",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "GT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey8": {Value: "value8", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireAtKey8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey8": {Value: "value8", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireAtKey8": {Value: "value8", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 9. Return 0 when GT flag is passed and key does not have an expiry time
|
{ // 9. Return 0 when GT flag is passed and key does not have an expiry time
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey9",
|
"EXPIREAT", "ExpireAtKey9",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "GT",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "GT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey9": {Value: "value9", ExpireAt: time.Time{}},
|
"ExpireAtKey9": {Value: "value9", ExpireAt: time.Time{}},
|
||||||
@@ -1361,42 +1366,42 @@ func Test_HandleEXPIREAT(t *testing.T) {
|
|||||||
{ // 10. Set expiry time when the provided time is before the current expiry time when LT flag is provided
|
{ // 10. Set expiry time when the provided time is before the current expiry time when LT flag is provided
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey10",
|
"EXPIREAT", "ExpireAtKey10",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "LT",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "LT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey10": {Value: "value10", ExpireAt: time.Now().Add(3000 * time.Second)},
|
"ExpireAtKey10": {Value: "value10", ExpireAt: mockClock.Now().Add(3000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey10": {Value: "value10", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey10": {Value: "value10", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 11. Return 0 when LT flag is passed and current expiry time is less than provided time
|
{ // 11. Return 0 when LT flag is passed and current expiry time is less than provided time
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey11",
|
"EXPIREAT", "ExpireAtKey11",
|
||||||
fmt.Sprintf("%d", time.Now().Add(3000*time.Second).Unix()), "LT",
|
fmt.Sprintf("%d", mockClock.Now().Add(3000*time.Second).Unix()), "LT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey11": {Value: "value11", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireAtKey11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedResponse: 0,
|
expectedResponse: 0,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey11": {Value: "value11", ExpireAt: time.Now().Add(1000 * time.Second)},
|
"ExpireAtKey11": {Value: "value11", ExpireAt: mockClock.Now().Add(1000 * time.Second)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
{ // 12. Return 0 when LT flag is passed and key does not have an expiry time
|
{ // 12. Return 0 when LT flag is passed and key does not have an expiry time
|
||||||
command: []string{
|
command: []string{
|
||||||
"EXPIREAT", "ExpireAtKey12",
|
"EXPIREAT", "ExpireAtKey12",
|
||||||
fmt.Sprintf("%d", time.Now().Add(1000*time.Second).Unix()), "LT",
|
fmt.Sprintf("%d", mockClock.Now().Add(1000*time.Second).Unix()), "LT",
|
||||||
},
|
},
|
||||||
presetValues: map[string]KeyData{
|
presetValues: map[string]KeyData{
|
||||||
"ExpireAtKey12": {Value: "value12", ExpireAt: time.Time{}},
|
"ExpireAtKey12": {Value: "value12", ExpireAt: time.Time{}},
|
||||||
},
|
},
|
||||||
expectedResponse: 1,
|
expectedResponse: 1,
|
||||||
expectedValues: map[string]KeyData{
|
expectedValues: map[string]KeyData{
|
||||||
"ExpireAtKey12": {Value: "value12", ExpireAt: time.Unix(time.Now().Add(1000*time.Second).Unix(), 0)},
|
"ExpireAtKey12": {Value: "value12", ExpireAt: time.Unix(mockClock.Now().Add(1000*time.Second).Unix(), 0)},
|
||||||
},
|
},
|
||||||
expectedError: nil,
|
expectedError: nil,
|
||||||
},
|
},
|
||||||
|
@@ -17,6 +17,7 @@ package generic
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -28,28 +29,28 @@ type SetParams struct {
|
|||||||
expireAt interface{} // Exact expireAt time un unix milliseconds
|
expireAt interface{} // Exact expireAt time un unix milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSetCommandParams(cmd []string, params SetParams) (SetParams, error) {
|
func getSetCommandParams(clock clock.Clock, cmd []string, params SetParams) (SetParams, error) {
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
switch strings.ToLower(cmd[0]) {
|
switch strings.ToLower(cmd[0]) {
|
||||||
case "get":
|
case "get":
|
||||||
params.get = true
|
params.get = true
|
||||||
return getSetCommandParams(cmd[1:], params)
|
return getSetCommandParams(clock, cmd[1:], params)
|
||||||
|
|
||||||
case "nx":
|
case "nx":
|
||||||
if params.exists != "" {
|
if params.exists != "" {
|
||||||
return SetParams{}, fmt.Errorf("cannot specify NX when %s is already specified", strings.ToUpper(params.exists))
|
return SetParams{}, fmt.Errorf("cannot specify NX when %s is already specified", strings.ToUpper(params.exists))
|
||||||
}
|
}
|
||||||
params.exists = "NX"
|
params.exists = "NX"
|
||||||
return getSetCommandParams(cmd[1:], params)
|
return getSetCommandParams(clock, cmd[1:], params)
|
||||||
|
|
||||||
case "xx":
|
case "xx":
|
||||||
if params.exists != "" {
|
if params.exists != "" {
|
||||||
return SetParams{}, fmt.Errorf("cannot specify XX when %s is already specified", strings.ToUpper(params.exists))
|
return SetParams{}, fmt.Errorf("cannot specify XX when %s is already specified", strings.ToUpper(params.exists))
|
||||||
}
|
}
|
||||||
params.exists = "XX"
|
params.exists = "XX"
|
||||||
return getSetCommandParams(cmd[1:], params)
|
return getSetCommandParams(clock, cmd[1:], params)
|
||||||
|
|
||||||
case "ex":
|
case "ex":
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
@@ -63,8 +64,8 @@ func getSetCommandParams(cmd []string, params SetParams) (SetParams, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return SetParams{}, errors.New("seconds value should be an integer")
|
return SetParams{}, errors.New("seconds value should be an integer")
|
||||||
}
|
}
|
||||||
params.expireAt = time.Now().Add(time.Duration(seconds) * time.Second)
|
params.expireAt = clock.Now().Add(time.Duration(seconds) * time.Second)
|
||||||
return getSetCommandParams(cmd[2:], params)
|
return getSetCommandParams(clock, cmd[2:], params)
|
||||||
|
|
||||||
case "px":
|
case "px":
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
@@ -78,8 +79,8 @@ func getSetCommandParams(cmd []string, params SetParams) (SetParams, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return SetParams{}, errors.New("milliseconds value should be an integer")
|
return SetParams{}, errors.New("milliseconds value should be an integer")
|
||||||
}
|
}
|
||||||
params.expireAt = time.Now().Add(time.Duration(milliseconds) * time.Millisecond)
|
params.expireAt = clock.Now().Add(time.Duration(milliseconds) * time.Millisecond)
|
||||||
return getSetCommandParams(cmd[2:], params)
|
return getSetCommandParams(clock, cmd[2:], params)
|
||||||
|
|
||||||
case "exat":
|
case "exat":
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
@@ -94,7 +95,7 @@ func getSetCommandParams(cmd []string, params SetParams) (SetParams, error) {
|
|||||||
return SetParams{}, errors.New("seconds value should be an integer")
|
return SetParams{}, errors.New("seconds value should be an integer")
|
||||||
}
|
}
|
||||||
params.expireAt = time.Unix(seconds, 0)
|
params.expireAt = time.Unix(seconds, 0)
|
||||||
return getSetCommandParams(cmd[2:], params)
|
return getSetCommandParams(clock, cmd[2:], params)
|
||||||
|
|
||||||
case "pxat":
|
case "pxat":
|
||||||
if len(cmd) < 2 {
|
if len(cmd) < 2 {
|
||||||
@@ -109,7 +110,7 @@ func getSetCommandParams(cmd []string, params SetParams) (SetParams, error) {
|
|||||||
return SetParams{}, errors.New("milliseconds value should be an integer")
|
return SetParams{}, errors.New("milliseconds value should be an integer")
|
||||||
}
|
}
|
||||||
params.expireAt = time.UnixMilli(milliseconds)
|
params.expireAt = time.UnixMilli(milliseconds)
|
||||||
return getSetCommandParams(cmd[2:], params)
|
return getSetCommandParams(clock, cmd[2:], params)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return SetParams{}, fmt.Errorf("unknown option %s for set command", strings.ToUpper(cmd[0]))
|
return SetParams{}, fmt.Errorf("unknown option %s for set command", strings.ToUpper(cmd[0]))
|
||||||
|
@@ -16,6 +16,7 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/echovault/echovault/internal/clock"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -33,6 +34,7 @@ type EchoVault interface {
|
|||||||
SetExpiry(ctx context.Context, key string, expire time.Time, touch bool)
|
SetExpiry(ctx context.Context, key string, expire time.Time, touch bool)
|
||||||
RemoveExpiry(key string)
|
RemoveExpiry(key string)
|
||||||
DeleteKey(ctx context.Context, key string) error
|
DeleteKey(ctx context.Context, key string) error
|
||||||
|
GetClock() clock.Clock
|
||||||
GetAllCommands() []Command
|
GetAllCommands() []Command
|
||||||
GetACL() interface{}
|
GetACL() interface{}
|
||||||
GetPubSub() interface{}
|
GetPubSub() interface{}
|
||||||
|
Reference in New Issue
Block a user