Skip to content

Commit 9347699

Browse files
committed
added sqlite engine
1 parent e884ed4 commit 9347699

File tree

5 files changed

+283
-1
lines changed

5 files changed

+283
-1
lines changed

commands_doc.go.wip

+53
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/tidwall/sjson"
1212
)
1313

14+
// dsetCommand - DSET <collection> <json> [<json> ...]
1415
func dsetCommand(c Context) {
1516
if len(c.args) < 2 {
1617
c.WriteError("DSET command requires at least 2 arguments: DSET <collection> [<json> <json> ...]")
@@ -71,6 +72,7 @@ func dsetCommand(c Context) {
7172
}
7273
}
7374

75+
// dgetCommand - DGET <collection> <docid>
7476
func dgetCommand(c Context) {
7577
if len(c.args) < 2 {
7678
c.WriteError("DGET requires at least 2 arguments: DGET <collection> <docid>")
@@ -88,6 +90,7 @@ func dgetCommand(c Context) {
8890
c.WriteBulkString(doc)
8991
}
9092

93+
// dgetallCommand - DGETALL <collection> [<offset> <limit>]
9194
func dgetallCommand(c Context) {
9295
if len(c.args) < 1 {
9396
c.WriteError("DGETALL command requires at least one argument: DGETALL <collection> [<offset> <limit>]")
@@ -133,7 +136,57 @@ func dgetallCommand(c Context) {
133136
}
134137

135138
c.WriteArray(len(data))
139+
for _, v := range data {
140+
c.WriteBulkString(v)
141+
}
142+
}
143+
144+
// dgetallbyindexCommand - DGETALLBYINDEX <collection> <index> <value> [<offset> <limit>]
145+
func dgetallbyindexCommand(c Context) {
146+
if len(c.args) < 1 {
147+
c.WriteError("DGETALL command requires at least one argument: DGETALL <collection> [<offset> <limit>]")
148+
return
149+
}
150+
151+
prefix := c.args[0] + "/{DOCUMENT}/{RAW}/"
152+
offset := prefix
153+
limit := 5
154+
155+
if len(c.args) > 1 && c.args[1] != "" {
156+
offset = prefix + c.args[1]
157+
}
158+
159+
if len(c.args) > 2 {
160+
limit, _ = strconv.Atoi(c.args[2])
161+
}
162+
163+
data := []string{}
164+
length := 0
165+
err := c.db.Scan(kvstore.ScannerOptions{
166+
FetchValues: true,
167+
IncludeOffset: offset == prefix,
168+
Prefix: prefix,
169+
Offset: offset,
170+
Handler: func(k, v string) bool {
171+
length++
172+
p := strings.SplitN(k, "/{DOCUMENT}/{RAW}/", 2)
173+
if len(p) < 2 {
174+
return true
175+
}
176+
data = append(data, p[1], v)
177+
if length >= limit {
178+
return false
179+
}
180+
return true
181+
},
182+
})
136183

184+
if err != nil {
185+
c.WriteError(err.Error())
186+
return
187+
}
188+
189+
c.WriteArray(len(data))
137190
for _, v := range data {
138191
c.WriteBulkString(v)
139192
}

helpers_db.go

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"path/filepath"
99
"strings"
1010

11+
"github.com/alash3al/redix/kvstore/sqlite"
12+
1113
"github.com/alash3al/redix/kvstore/null"
1214

1315
"github.com/alash3al/redix/kvstore"
@@ -46,6 +48,8 @@ func openDB(engine, dbpath string) (kvstore.DB, error) {
4648
return leveldb.OpenLevelDB(dbpath)
4749
case "null":
4850
return null.OpenNull()
51+
case "sqlite":
52+
return sqlite.OpenSQLite(dbpath)
4953
}
5054
}
5155

init.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func init() {
6565

6666
// initDBs - initialize databases from the disk for faster access
6767
func initDBs() {
68-
os.MkdirAll(*flagStorageDir, 0644)
68+
os.MkdirAll(*flagStorageDir, 0755)
6969

7070
dirs, _ := ioutil.ReadDir(*flagStorageDir)
7171

kvstore/sqlite/sqlite.go

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright 2018 The Redix Authors. All rights reserved.
2+
// Use of this source code is governed by a Apache 2.0
3+
// license that can be found in the LICENSE file.
4+
//
5+
// null is a db engine based on `/dev/null` style
6+
package sqlite
7+
8+
import (
9+
"database/sql"
10+
"errors"
11+
"fmt"
12+
"os"
13+
"strconv"
14+
"strings"
15+
"sync"
16+
"time"
17+
18+
_ "github.com/mattn/go-sqlite3"
19+
20+
"github.com/alash3al/redix/kvstore"
21+
)
22+
23+
// SQLite - represents a SQLite db implementation
24+
type SQLite struct {
25+
db *sql.DB
26+
filename string
27+
sync.RWMutex
28+
}
29+
30+
// OpenSQLite - Opens the specified path
31+
func OpenSQLite(dbpath string) (*SQLite, error) {
32+
db, err := sql.Open("sqlite3", dbpath+"?cache=shared&_journal=wal")
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
if _, err := db.Exec(`
38+
CREATE TABLE IF NOT EXISTS data (
39+
_id INTEGER PRIMARY KEY,
40+
_key TEXT NOT NULL UNIQUE,
41+
_value TEXT NOT NULL,
42+
_expires INTEGER
43+
);
44+
`); err != nil {
45+
return nil, err
46+
}
47+
48+
lite := new(SQLite)
49+
lite.filename = dbpath
50+
lite.db = db
51+
52+
lite.db.Exec("PRAGMA synchronous = OFF")
53+
lite.db.Exec("PRAGMA journal_mode = OFF")
54+
lite.db.Exec(fmt.Sprintf("PRAGMA page_size = %d", 20*1024*1024))
55+
56+
lite.GC()
57+
58+
return lite, nil
59+
}
60+
61+
// Size - returns the size of the database in bytes
62+
func (db *SQLite) Size() int64 {
63+
stat, err := os.Stat(db.filename)
64+
if err != nil {
65+
return 0
66+
}
67+
return stat.Size()
68+
}
69+
70+
// GC - runs the garbage collector
71+
func (db *SQLite) GC() error {
72+
_, err := db.db.Exec("VACUUM")
73+
return err
74+
}
75+
76+
// Incr - increment the key by the specified value
77+
func (db *SQLite) Incr(k string, by int64) (int64, error) {
78+
db.Lock()
79+
defer db.Unlock()
80+
81+
val, err := db.Get(k)
82+
if err != nil {
83+
val = ""
84+
}
85+
86+
valInt, _ := strconv.ParseInt(val, 10, 64)
87+
valInt += by
88+
89+
err = db.Set(k, strconv.FormatInt(valInt, 10), -1)
90+
if err != nil {
91+
return 0, err
92+
}
93+
94+
return valInt, nil
95+
}
96+
97+
// Set - sets a key with the specified value and optional ttl
98+
func (db *SQLite) Set(k, v string, ttl int) error {
99+
var expires int64
100+
101+
if ttl < 1 {
102+
expires = 0
103+
} else {
104+
expires = time.Now().Add(time.Duration(ttl) * time.Millisecond).Unix()
105+
}
106+
107+
_, err := db.db.Exec("INSERT OR REPLACE INTO data(_key, _value, _expires) VALUES(?, ?, ?)", k, v, expires)
108+
return err
109+
}
110+
111+
// MSet - sets multiple key-value pairs
112+
func (db *SQLite) MSet(data map[string]string) error {
113+
args := []interface{}{}
114+
values := []string{}
115+
for k, v := range data {
116+
values = append(values, "(?, ?, ?)")
117+
args = append(args, k, v, 0)
118+
}
119+
_, err := db.db.Exec("INSERT OR REPLACE INTO data(_key, _value, _expires) VALUES"+strings.Join(values, ", "), args...)
120+
return err
121+
}
122+
123+
// Get - fetches the value of the specified k
124+
func (db *SQLite) Get(k string) (string, error) {
125+
var value string
126+
var expires int64
127+
128+
if err := db.db.QueryRow("SELECT _value, _expires FROM data WHERE _key = ?", k).Scan(&value, &expires); err != nil {
129+
return "", err
130+
}
131+
132+
now := time.Now().Unix()
133+
134+
if now >= expires && expires > 0 {
135+
db.db.Exec("DELETE FROM data WHERE _key = ?", k)
136+
return "", errors.New("key not found")
137+
}
138+
139+
return value, nil
140+
}
141+
142+
// MGet - fetch multiple values of the specified keys
143+
func (db *SQLite) MGet(keys []string) (data []string) {
144+
for _, key := range keys {
145+
v, _ := db.Get(key)
146+
data = append(data, v)
147+
}
148+
return data
149+
}
150+
151+
// TTL - returns the time to live of the specified key's value
152+
func (db *SQLite) TTL(key string) int64 {
153+
var value string
154+
var expires int64
155+
156+
if err := db.db.QueryRow("SELECT _value, _expires FROM data WHERE _key = ?", key).Scan(&value, &expires); err != nil {
157+
return -2
158+
}
159+
160+
if expires < 1 {
161+
return -1
162+
}
163+
164+
now := time.Now().Unix()
165+
166+
if now >= expires {
167+
return -2
168+
}
169+
170+
return (expires - now)
171+
}
172+
173+
// Del - removes key(s) from the store
174+
func (db *SQLite) Del(keys []string) error {
175+
for _, k := range keys {
176+
db.db.Exec("DELETE FROM data WHERE _key = ?", k)
177+
}
178+
return nil
179+
}
180+
181+
// Scan - iterate over the whole store using the handler function
182+
func (db *SQLite) Scan(scannerOpt kvstore.ScannerOptions) error {
183+
sql := "SELECT _key, _value FROM data"
184+
wheres := []string{}
185+
args := []interface{}{}
186+
187+
if scannerOpt.Offset != "" {
188+
op := ">"
189+
if scannerOpt.IncludeOffset {
190+
op = ">="
191+
}
192+
wheres = append(wheres, "_id "+(op)+" (SELECT _id FROM data WHERE _key LIKE ?)")
193+
args = append(args, scannerOpt.Offset+"%")
194+
}
195+
196+
if scannerOpt.Prefix != "" {
197+
wheres = append(wheres, "_key LIKE ?")
198+
args = append(args, scannerOpt.Prefix+"%")
199+
}
200+
201+
if len(wheres) > 0 {
202+
sql += " WHERE (" + strings.Join(wheres, ") AND (") + ")"
203+
}
204+
205+
rows, err := db.db.Query(sql, args...)
206+
if err != nil {
207+
return err
208+
}
209+
defer rows.Close()
210+
211+
for rows.Next() {
212+
var k, v string
213+
214+
if err := rows.Scan(&k, &v); err != nil {
215+
return err
216+
}
217+
218+
if next := scannerOpt.Handler(k, v); !next {
219+
break
220+
}
221+
}
222+
223+
return nil
224+
}

vars.go

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ var (
116116
"boltdb": true,
117117
"leveldb": true,
118118
"null": true,
119+
"sqlite": true,
119120
}
120121
engineOptions = url.Values{}
121122
defaultPubSubAllTopic = "*"

0 commit comments

Comments
 (0)