2023-09-03 04:03:09 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
|
|
|
import std/os
|
|
|
|
import stew/results
|
|
|
|
import ./abstract_db_transaction
|
|
|
|
|
|
|
|
import nimdbx/[Database, CRUD, Collection, Transaction, Cursor, Error, Index, Collatable, Data]
|
|
|
|
|
|
|
|
export Database, CRUD, Collection, Transaction, Cursor, Error, Index, Collatable, Data
|
|
|
|
|
|
|
|
type
|
|
|
|
MDBXStoreRef* = ref object of RootObj
|
|
|
|
database* {.requiresInit.}: Database
|
2023-09-03 17:52:35 +00:00
|
|
|
raftNodesData* {.requiresInit.}: Collection
|
2023-09-03 04:03:09 +00:00
|
|
|
|
|
|
|
MDBXTransaction* = ref object of RootObj
|
|
|
|
transaction: CollectionTransaction
|
|
|
|
|
|
|
|
const
|
2023-09-03 17:52:35 +00:00
|
|
|
MaxFileSize = 1024 * 1024 * 400 #r: 100MB (MDBX default is 400 MB)
|
2023-09-03 04:03:09 +00:00
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
# MDBX exception handling helper templates
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template handleEx(body: untyped) =
|
|
|
|
## Handle and convert MDBX exceptions to Result
|
|
|
|
try:
|
|
|
|
body
|
|
|
|
except MDBXError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except OSError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except CatchableError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
|
|
|
|
template handleExEx(body: untyped) =
|
|
|
|
## Handle and convert MDBX exceptions to Result
|
|
|
|
try:
|
|
|
|
body
|
|
|
|
except MDBXError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except OSError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except CatchableError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except Exception as e:
|
|
|
|
return err(e.msg)
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
# MDBX transactions methods
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc commit*(t: MDBXTransaction): ADbTResult[void] =
|
|
|
|
handleEx():
|
|
|
|
t.transaction.commit()
|
|
|
|
ok()
|
|
|
|
|
|
|
|
proc rollback*(t: MDBXTransaction): ADbTResult[void] =
|
|
|
|
handleEx():
|
|
|
|
t.transaction.abort()
|
|
|
|
ok()
|
|
|
|
|
|
|
|
proc beginDbTransaction*(db: MDBXStoreRef): ADbTResult[MDBXTransaction] =
|
2023-09-03 17:52:35 +00:00
|
|
|
if db.raftNodesData != nil:
|
2023-09-03 04:03:09 +00:00
|
|
|
handleEx():
|
2023-09-03 17:52:35 +00:00
|
|
|
ok(MDBXTransaction(transaction: db.raftNodesData.beginTransaction()))
|
2023-09-03 04:03:09 +00:00
|
|
|
else:
|
2023-09-03 17:52:35 +00:00
|
|
|
err("MDBXStoreRef.raftNodesData is nil")
|
2023-09-03 04:03:09 +00:00
|
|
|
|
|
|
|
proc put*(t: MDBXTransaction, key, value: openArray[byte]): ADbTResult[void] =
|
|
|
|
handleExEx():
|
|
|
|
t.transaction.put(asData(key), asData(value))
|
|
|
|
ok()
|
|
|
|
|
|
|
|
proc del*(t: MDBXTransaction, key: openArray[byte]): ADbTResult[void] =
|
|
|
|
handleExEx():
|
|
|
|
t.transaction.del(asData(key))
|
|
|
|
ok()
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
# MDBX transactions convenience templates
|
|
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template checkDbChainsNotNil(db: MDBXStoreRef, body: untyped) =
|
2023-09-03 17:52:35 +00:00
|
|
|
## Check if db.raftNodesData is not nil and execute the body
|
2023-09-03 04:03:09 +00:00
|
|
|
## if it is not nil. Otherwise, raise an assert.
|
|
|
|
##
|
2023-09-03 17:52:35 +00:00
|
|
|
if db.raftNodesData != nil:
|
2023-09-03 04:03:09 +00:00
|
|
|
body
|
|
|
|
else:
|
2023-09-03 17:52:35 +00:00
|
|
|
raiseAssert "MDBXStoreRef.raftNodesData is nil"
|
2023-09-03 04:03:09 +00:00
|
|
|
|
|
|
|
template withDbSnapshot*(db: MDBXStoreRef, body: untyped) =
|
|
|
|
## Create an MDBX snapshot and execute the body providing
|
|
|
|
## a snapshot variable cs for the body statements to operate on.
|
|
|
|
## Finish the snapshot after the body is executed.
|
|
|
|
##
|
|
|
|
##
|
|
|
|
checkDbChainsNotNil(db):
|
|
|
|
handleEx():
|
2023-09-03 17:52:35 +00:00
|
|
|
let cs {.inject.} = db.raftNodesData.beginSnapshot()
|
2023-09-03 04:03:09 +00:00
|
|
|
defer: cs.finish()
|
|
|
|
body
|
|
|
|
|
|
|
|
template withDbTransaction*(db: MDBXStoreRef, body: untyped) =
|
|
|
|
## Create an MDBX transaction and execute the body and inject
|
|
|
|
## a transaction variable dbTransaction in the body statements.
|
|
|
|
## Handle MDBX errors and abort the transaction on error.
|
|
|
|
##
|
|
|
|
checkDbChainsNotNil(db):
|
|
|
|
handleEx():
|
2023-09-03 17:52:35 +00:00
|
|
|
var dbTransaction {.inject.} = db.raftNodesData.beginTransaction()
|
2023-09-03 04:03:09 +00:00
|
|
|
defer: dbTransaction.commit()
|
|
|
|
try:
|
|
|
|
body
|
|
|
|
except MDBXError as e:
|
|
|
|
dbTransaction.abort()
|
|
|
|
return err(e.msg)
|
|
|
|
except OSError as e:
|
|
|
|
dbTransaction.abort()
|
|
|
|
return err(e.msg)
|
|
|
|
except Exception as e:
|
|
|
|
dbTransaction.abort()
|
|
|
|
return err(e.msg)
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------
|
|
|
|
# MDBX KvStore interface implementation
|
|
|
|
# ------------------------------------------------------------------------------------------
|
2023-09-03 17:52:35 +00:00
|
|
|
type
|
|
|
|
DataProc = proc(data: seq[byte])
|
|
|
|
FindProc = proc(data: seq[byte])
|
2023-09-03 04:03:09 +00:00
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc get*(db: MDBXStoreRef, key: openArray[byte], onData: DataProc): Result[bool, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("mdbx: key cannot be empty on get")
|
|
|
|
|
|
|
|
withDbSnapshot(db):
|
|
|
|
let mdbxData = asByteSeq(cs.get(asData(key)))
|
|
|
|
if mdbxData.len > 0:
|
|
|
|
onData(mdbxData)
|
|
|
|
return ok(true)
|
|
|
|
else:
|
|
|
|
return ok(false)
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc find*(db: MDBXStoreRef, prefix: openArray[byte], onFind: FindProc): Result[int, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
raiseAssert "Unimplemented"
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc put*(db: MDBXStoreRef, key, value: openArray[byte]): Result[void, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("mdbx: key cannot be empty on get")
|
|
|
|
|
|
|
|
withDbTransaction(db):
|
|
|
|
dbTransaction.put(asData(key), asData(value))
|
|
|
|
ok()
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc contains*(db: MDBXStoreRef, key: openArray[byte]): Result[bool, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("mdbx: key cannot be empty on get")
|
|
|
|
|
|
|
|
withDbSnapshot(db):
|
|
|
|
let mdbxData = asByteSeq(cs.get(asData(key)))
|
|
|
|
if mdbxData.len > 0:
|
|
|
|
return ok(true)
|
|
|
|
else:
|
|
|
|
return ok(false)
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc del*(db: MDBXStoreRef, key: openArray[byte]): Result[bool, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
if key.len <= 0:
|
|
|
|
return err("mdbx: key cannot be empty on del")
|
|
|
|
|
|
|
|
withDbTransaction(db):
|
|
|
|
let mdbxData = asByteSeq(dbTransaction.get(asData(key)))
|
|
|
|
if mdbxData.len > 0:
|
|
|
|
dbTransaction.del(asData(key))
|
|
|
|
return ok(true)
|
|
|
|
else:
|
|
|
|
return ok(false)
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
proc clear*(db: MDBXStoreRef): Result[bool, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
raiseAssert "Unimplemented"
|
|
|
|
|
|
|
|
proc close*(db: MDBXStoreRef) =
|
|
|
|
try:
|
|
|
|
db.database.close()
|
|
|
|
except MDBXError as e:
|
|
|
|
raiseAssert e.msg
|
|
|
|
except OSError as e:
|
|
|
|
raiseAssert e.msg
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------
|
|
|
|
# .End. MDBX KvStore interface implementation
|
|
|
|
# ------------------------------------------------------------------------------------------
|
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
# proc bulkPutSortedData*[KT: ByteArray32 | ByteArray33](db: MDBXStoreRef, keys: openArray[KT], vals: openArray[seq[byte]]): Result[int64] =
|
|
|
|
# if keys.len <= 0:
|
|
|
|
# return err("mdbx: keys cannot be empty on bulkPutSortedData")
|
2023-09-03 04:03:09 +00:00
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
# if keys.len != vals.len:
|
|
|
|
# return err("mdbx: keys and vals must have the same length")
|
2023-09-03 04:03:09 +00:00
|
|
|
|
2023-09-03 17:52:35 +00:00
|
|
|
# withDbTransaction(db):
|
|
|
|
# for i in 0 ..< keys.len:
|
|
|
|
# dbTransaction.put(asData(keys[i]), asData(vals[i]))
|
|
|
|
# return ok(0)
|
2023-09-03 04:03:09 +00:00
|
|
|
|
|
|
|
proc init*(
|
|
|
|
T: type MDBXStoreRef, basePath: string, name: string,
|
2023-09-03 17:52:35 +00:00
|
|
|
readOnly = false): Result[T, string] =
|
2023-09-03 04:03:09 +00:00
|
|
|
let
|
|
|
|
dataDir = basePath / name / "data"
|
|
|
|
backupsDir = basePath / name / "backups" # Do we need that in case of MDBX? Should discuss this with @zah
|
|
|
|
|
|
|
|
try:
|
|
|
|
createDir(dataDir)
|
|
|
|
createDir(backupsDir)
|
|
|
|
except OSError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
except IOError as e:
|
|
|
|
return err(e.msg)
|
|
|
|
|
|
|
|
var
|
|
|
|
mdbxFlags = {Exclusive, SafeNoSync}
|
|
|
|
if readOnly:
|
|
|
|
mdbxFlags.incl(ReadOnly)
|
|
|
|
|
|
|
|
handleEx():
|
|
|
|
let
|
|
|
|
db = openDatabase(dataDir, flags=mdbxFlags, maxFileSize=MaxFileSize)
|
2023-09-03 17:52:35 +00:00
|
|
|
raftNodesData = createCollection(db, "raftNodesData", StringKeys, BlobValues)
|
|
|
|
ok(T(database: db, raftNodesData: raftNodesData))
|