Skip to content
This repository has been archived by the owner on Apr 2, 2021. It is now read-only.

Commit

Permalink
Feature/expiredkeys pt1 (#86)
Browse files Browse the repository at this point in the history
* set nil on not found

* set expiration

* comment

* go import

* test updated
  • Loading branch information
kasvith authored Aug 28, 2018
1 parent 7cbc516 commit 618ba80
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 26 deletions.
1 change: 1 addition & 0 deletions internal/arch/command-table.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var CommandTable = map[string]Command{
"exists": {ModifyKeySpace: false, Fn: cmds.Exists, MinArgs: 1, MaxArgs: 1},
"del": {ModifyKeySpace: true, Fn: cmds.Del, MinArgs: 1, MaxArgs: -1},
"keys": {ModifyKeySpace: false, Fn: cmds.Keys, MinArgs: 0, MaxArgs: 0},
"expire": {ModifyKeySpace: false, Fn: cmds.Expire, MinArgs: 2, MaxArgs: 2},

// strings
"get": {ModifyKeySpace: false, Fn: cmds.Get, MinArgs: 1, MaxArgs: 1},
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestCli(t *testing.T) {
// strings
{
// get not found
runTestSendRecv(t, "get a", "(error) a not found")
runTestSendRecv(t, "get a", "(null)")

// set
runTestSendRecv(t, "set a 1", `"OK"`)
Expand Down
25 changes: 25 additions & 0 deletions internal/cmds/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@
package cmds

import (
"errors"
"strconv"
"time"

"github.com/kasvith/kache/internal/db"
"github.com/kasvith/kache/internal/protcl"
"github.com/kasvith/kache/internal/sys"
)

// Exists will check for key existency in given db
Expand All @@ -46,3 +51,23 @@ func Keys(d *db.DB, args []string) *protcl.Resp3 {
keys := d.Keys()
return &protcl.Resp3{Type: protcl.Resp3Array, Elems: keys}
}

// Expire a key
func Expire(d *db.DB, args []string) *protcl.Resp3 {
if v, ok := d.GetNode(args[0]); ok {
val, err := strconv.Atoi(args[1])
if err != nil {
return &protcl.Resp3{Type: protcl.Resp3SimpleError, Err: &protcl.ErrCastFailedToInt{Val: args[1]}}
}

if val < 0 {
return &protcl.Resp3{Type: protcl.Resp3SimpleError, Err: errors.New("invalid seconds")}
}

ttl := sys.GetTTL(int64(val), time.Second)
v.SetExpiration(ttl)
return &protcl.Resp3{Type: protcl.Resp3Number, Integer: 1}
}

return &protcl.Resp3{Type: protcl.Resp3Number, Integer: 0}
}
2 changes: 1 addition & 1 deletion internal/cmds/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
func Get(d *db.DB, args []string) *protcl.Resp3 {
val, err := d.Get(args[0])
if err != nil {
return &protcl.Resp3{Type: protcl.Resp3BolbError, Err: err}
return &protcl.Resp3{Type: protcl.Resp3Null}
}

if val.Type != db.TypeString {
Expand Down
72 changes: 51 additions & 21 deletions internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,33 @@ func NewDB() *DB {
return &DB{file: make(map[string]*DataNode)}
}

// Get the value of a key
func (db *DB) Get(key string) (*DataNode, error) {
// GetNode will clear the key if its expired
func (db *DB) GetNode(key string) (*DataNode, bool) {
db.mux.RLock()

if v, ok := db.file[key]; ok {
db.mux.RUnlock()
return v, nil
if v.IsExpired() {
db.mux.Lock()
delete(db.file, key)
db.mux.Unlock()

return nil, false
}

return v, true
}

db.mux.RUnlock()
return nil, false
}

// Get the value of a key
func (db *DB) Get(key string) (*DataNode, error) {
if v, ok := db.GetNode(key); ok {
return v, nil
}

return nil, &KeyNotFoundError{key: key}
}

Expand All @@ -71,17 +88,16 @@ func (db *DB) Set(key string, val *DataNode) {
db.mux.Unlock()
}

// GetIfNotSet will try to get the key if not will set it to a given value
// GetIfNotSet will try to GetNode the key if not will set it to a given value
func (db *DB) GetIfNotSet(key string, val *DataNode) (value *DataNode, found bool) {
db.mux.Lock()

if v, found := db.file[key]; found {
db.mux.Unlock()
if v, found := db.GetNode(key); found {
return v, true
}
db.file[key] = val

db.mux.Lock()
db.file[key] = val
db.mux.Unlock()

return val, false
}

Expand All @@ -90,39 +106,53 @@ func (db *DB) Del(keys []string) int {
db.mux.Lock()
del := 0
for _, k := range keys {
if _, ok := db.file[k]; ok {
if v, ok := db.file[k]; ok {
// dont count already deleted keys aka expired
if !v.IsExpired() {
del++
}
delete(db.file, k)
del++
}
}
db.mux.Unlock()

return del
}

// Exists finds the existancy of a key
// Exists finds the existence of a key
func (db *DB) Exists(key string) int {
db.mux.RLock()
if _, ok := db.file[key]; ok {
db.mux.RUnlock()
if _, ok := db.GetNode(key); ok {
return 1
}

db.mux.RUnlock()
return 0
}

// Keys returns all keys of the db
func (db *DB) Keys() []*protcl.Resp3 {
db.mux.RLock()

keys := make([]*protcl.Resp3, len(db.file))
i := 0
for key := range db.file {
keys[i] = &protcl.Resp3{Type: protcl.Resp3BlobString, Str: key}
i++
keys := make([]*protcl.Resp3, 0)
for key, val := range db.file {
if !val.IsExpired() {
keys = append(keys, &protcl.Resp3{Type: protcl.Resp3BlobString, Str: key})
}

}

db.mux.RUnlock()
return keys
}

// SetExpire time for a key
func (db *DB) SetExpire(key string, ttl int64) bool {
if ttl < 0 && ttl != -1 {
return false
}

if v, ok := db.GetNode(key); ok {
v.SetExpiration(ttl)
}

return false
}
29 changes: 26 additions & 3 deletions internal/db/storg.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@

package db

import (
"sync"
"time"
)

// DataType is a enum type which holds the type of data
type DataType int

Expand All @@ -47,13 +52,31 @@ type DataNode struct {
Type DataType

// ExpiresAt for the data(unix timestamp)
ExpiresAt int
ExpiresAt int64

// Value of the data
Value interface{}

// rw mux
mux sync.RWMutex
}

// NewDataNode creates a new *DataNode
func NewDataNode(t DataType, exp int, val interface{}) *DataNode {
return &DataNode{t, exp, val}
func NewDataNode(t DataType, exp int64, val interface{}) *DataNode {
return &DataNode{Type: t, ExpiresAt: exp, Value: val}
}

// IsExpired will be true when node expired
func (node *DataNode) IsExpired() bool {
node.mux.RLock()
expired := node.ExpiresAt != -1 && node.ExpiresAt < time.Now().Unix()
node.mux.RUnlock()
return expired
}

// SetExpiration will set the expiration for the node
func (node *DataNode) SetExpiration(ttl int64) {
node.mux.Lock()
node.ExpiresAt = ttl
node.mux.Unlock()
}
35 changes: 35 additions & 0 deletions internal/sys/expire.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* MIT License
*
* Copyright (c) 2018 Kasun Vithanage
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package sys

import (
"time"
)

// GetTTL unix time stamp for a key
func GetTTL(val int64, scale time.Duration) int64 {
tt := time.Now().Add(time.Duration(val) * scale).Unix()
return tt
}

0 comments on commit 618ba80

Please sign in to comment.