Hi, just picked Nim as a new language to learn. I think it is fantastic, having used Go for quite some time it is refreshing not having to write all that boilerplate for everything.
Now, I need a little key/value store with basic persistence for a toy web application.
Come up with this. Use nim-lmdb by Federico Ceratto (then I discovered there is a higher level library using the former as a base: LimDB by Carlo Capocasa).
I don't want to make importers of this module preoccupy of managing db connections.
Question is: It is a big performances hit or blasphemy to manage them inside every procedure? I'd like to have only that three procedures as an interface...
What do you think? Alternatives?
Remember this a toy, but I don't want it to be an... obscene toy... (a sex toy?)
import lmdb
proc store*(key, value: string) =
let dbenv = newLMDBEnv("./testdb")
let txn = dbenv.newTxn()
let dbi = txn.dbiOpen("", 0)
txn.put(dbi, key, value)
txn.commit()
dbenv.close(dbi)
dbenv.envClose()
proc retrieve*(key: string): (string, bool) =
let dbenv = newLMDBEnv("./testdb")
let txn = dbenv.newTxn()
let dbi = txn.dbiOpen("", 0)
try:
result = (txn.get(dbi, key), true)
txn.commit()
except:
result = ("", false)
txn.abort()
finally:
dbenv.close(dbi)
dbenv.envClose()
proc destroy*(key: string): bool =
let dbenv = newLMDBEnv("./testdb")
let txn = dbenv.newTxn()
let dbi = txn.dbiOpen("", 0)
try:
let value = txn.get(dbi, key)
txn.del(dbi, key, value)
result = true
txn.commit()
except:
result = false
txn.abort()
finally:
dbenv.close(dbi)
dbenv.envClose()
store("foo", "baz")
assert retrieve("foo") == ("baz", true)
store("foo", "bar")
assert retrieve("foo") == ("bar", true)
assert retrieve("nope") == ("", false)
assert destroy("foo") == true
assert retrieve("foo") == ("", false)
assert destroy("fooooooo") == false
You can probably just create an object type which will hold the database connection, and simply have it as an argument for your procedures, something like (kinda pseudocode):
type
DatabaseConnection* = object
env: LMDBEnv
proc newCon*(file: string): DatabaseConnection =
## connect stuff
# ...
proc store*(db: DatabaseConnection, key, value: string) =
## store stuff
# ...
# etc
And then the users will be able to use it as
let con = newCon("./testdb")
con.store("key", "val")
You can probably just create an object type which will hold the database connection, and simply have it as an argument for your procedures
That's what I opted for using for my wallpaper changer, I just pass around a DBconnection, and that works nice.
Thanks guys, the following apparently works. Is this design ok? Any obvious pitfalls?
Contents of file kvstore.nim
import lmdb
type
Con* = LMDBEnv
proc newCon*(file: string): Con =
return newLMDBEnv(file)
proc closeCon*(con: LMDBEnv) =
con.envClose()
proc store*(con: Con, key, value: string): bool =
let txn = con.newTxn()
let dbi = txn.dbiOpen("", 0)
try:
txn.put(dbi, key, value)
result = true
except:
result = false
finally:
txn.commit()
con.close(dbi)
proc retrieve*(con: Con, key: string): (string, bool) =
let txn = con.newTxn()
let dbi = txn.dbiOpen("", 0)
try:
result = (txn.get(dbi, key), true)
txn.commit()
except:
result = ("", false)
txn.abort()
finally:
con.close(dbi)
proc destroy*(con: Con, key: string): bool =
let txn = con.newTxn()
let dbi = txn.dbiOpen("", 0)
try:
let value = txn.get(dbi, key)
txn.del(dbi, key, value)
result = true
txn.commit()
except:
result = false
txn.abort()
finally:
con.close(dbi)
let db = newCon("testdb")
assert db.store("foo", "baz") == true
assert db.retrieve("foo") == ("baz", true)
assert db.store("foo", "bar") == true
assert db.retrieve("foo") == ("bar", true)
assert db.retrieve("nope") == ("", false)
assert db.destroy("foo") == true
assert db.retrieve("foo") == ("", false)
assert db.destroy("fooooooo") == false
db.closeCon()
Contents of file webapp.nim
import kvstore
import jester
let db = newCon("testdb")
routes:
get "/store/@key/@value":
if db.store(@"key", @"value"):
resp "ok"
else:
resp "fail"
get "/retrieve/@key":
let (value, found) = db.retrieve(@"key")
if found:
resp value
else:
resp "fail"
get "/destroy/@key":
if db.destroy(@"key"):
resp "ok"
else:
resp "fail"
db.closeCon
Testing webapp.nim with curl
curl localhost:5000/store/what_i_want/pizza
ok
curl localhost:5000/store/what_i_really_want/more_pizza
ok
curl localhost:5000/retrieve/what_i_want
pizza
curl localhost:5000/retrieve/what_i_really_want
more_pizza
curl localhost:5000/store/sblaytiful/nobody_understands_me
ok
curl localhost:5000/destroy/sblaytiful
ok
curl localhost:5000/retrieve/sblaytiful
fail (expected)