Adding mounted store (#33)

* adding monted store

* misc spelling

* adding mounted store tests to suite

* split out key

* relaxed key initialization

* always mount and lookup by path

* cleaned up and reorged tests

* test lookup by path

* add re-exports

* more re-exports

* fix warnings and re-exports
This commit is contained in:
Dmitriy Ryajov 2022-09-21 20:13:50 -04:00 committed by GitHub
parent 446de6f978
commit f5dadd93be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 601 additions and 331 deletions

View File

@ -1,3 +1,7 @@
import ./datastore/datastore
import ./datastore/fsds
import ./datastore/sql
import ./datastore/mountedds
import ./datastore/tieredds
export datastore
export datastore, fsds, mountedds, tieredds, sql

View File

@ -1,5 +1,4 @@
import pkg/chronos
import pkg/questionable
import pkg/questionable/results
import pkg/upraises
@ -28,6 +27,6 @@ method close*(self: Datastore): Future[?!void] {.base, async, locks: "unknown".}
method query*(
self: Datastore,
query: Query): Future[?!QueryIter] {.gcsafe.} =
query: Query): Future[?!QueryIter] {.base, gcsafe.} =
raiseAssert("Not implemented!")

View File

@ -1,231 +1,3 @@
import std/algorithm
import std/hashes
import std/oids
import std/sequtils
import std/strutils
import ./key/key
import pkg/questionable
import pkg/questionable/results
from pkg/stew/results as stewResults import get, isErr
import pkg/upraises
export hashes
push: {.upraises: [].}
type
Namespace* = object
field*: string
value*: string
Key* = object
namespaces*: seq[Namespace]
const
Delimiter* = ":"
Separator* = "/"
# TODO: operator/s for combining string|Namespace,string|Namespace
# TODO: lifting from ?![Namespace|Key] for various ops
func init*(
T: type Namespace,
field, value: string): ?!T =
if value.strip == "":
return failure "value string must not be all whitespace or empty"
if value.contains(Delimiter):
return failure "value string must not contain Delimiter \"" &
Delimiter & "\""
if value.contains(Separator):
return failure "value string must not contain Separator \"" &
Separator & "\""
if field != "":
if field.strip == "":
return failure "field string must not be all whitespace"
if field.contains(Delimiter):
return failure "field string must not contain Delimiter \"" &
Delimiter & "\""
if field.contains(Separator):
return failure "field string must not contain Separator \"" &
Separator & "\""
success T(field: field, value: value)
func init*(T: type Namespace, id: string): ?!T =
if id.strip == "":
return failure "id string must not be all whitespace or empty"
if id.contains(Separator):
return failure "id string must not contain Separator \"" & Separator & "\""
if id == Delimiter:
return failure "value in id string \"[field]" & Delimiter &
"[value]\" must not be empty"
if id.count(Delimiter) > 1:
return failure "id string must not contain more than one Delimiter \"" &
Delimiter & "\""
let
(field, value) = block:
let parts = id.split(Delimiter)
if parts.len > 1:
(parts[0], parts[^1])
else:
("", parts[^1])
T.init(field, value)
func id*(self: Namespace): string =
if self.field.len > 0:
self.field & Delimiter & self.value
else:
self.value
func hash*(namespace: Namespace): Hash =
hash(namespace.id)
func `$`*(namespace: Namespace): string =
"Namespace(" & namespace.id & ")"
func init*(T: type Key, namespaces: varargs[Namespace]): ?!T =
if namespaces.len == 0:
return failure "namespaces must contain at least one Namespace"
success T(namespaces: @namespaces)
func init*(T: type Key, namespaces: varargs[string]): ?!T =
if namespaces.len == 0:
return failure "namespaces must contain at least one Namespace id string"
success T(
namespaces: namespaces.mapIt(
?Namespace.init(it)
))
func init*(T: type Key, keys: varargs[Key]): ?!T =
if keys.len == 0:
return failure "No keys provided"
success T(
namespaces: keys.mapIt(it.namespaces).mapIt(it).concat)
func init*(T: type Key, id: string): ?!T =
if id == "":
return failure "id string must contain at least one Namespace"
if id.strip == "":
return failure "id string must not be all whitespace"
let
nsStrs = id.split(Separator).filterIt(it != "")
if nsStrs.len == 0:
return failure "id string must not contain more than one Separator " &
"\"" & Separator & "\""
Key.init(nsStrs)
func list*(self: Key): seq[Namespace] =
self.namespaces
proc random*(T: type Key): string =
$genOid()
template `[]`*(key: Key, x: auto): auto =
key.namespaces[x]
func len*(self: Key): int =
self.namespaces.len
iterator items*(key: Key): Namespace =
for k in key.namespaces:
yield k
func reversed*(self: Key): Key =
Key(namespaces: self.namespaces.reversed)
func reverse*(self: Key): Key =
self.reversed
func name*(self: Key): string =
if self.len > 0:
return self[^1].value
func `type`*(self: Key): string =
if self.len > 0:
return self[^1].field
func id*(self: Key): string =
Separator & self.namespaces.mapIt(it.id).join(Separator)
func root*(self: Key): bool =
self.len == 1
func parent*(self: Key): ?!Key =
if self.root:
failure "key has no parent"
else:
success Key(namespaces: self.namespaces[0..^2])
func path*(self: Key): ?!Key =
let
parent = ?self.parent
if self[^1].field == "":
return success parent
let ns = parent.namespaces & @[Namespace(value: self[^1].field)]
success Key(namespaces: ns)
func child*(self: Key, ns: Namespace): Key =
Key(namespaces: self.namespaces & @[ns])
func `/`*(self: Key, ns: Namespace): Key =
self.child(ns)
func child*(self: Key, namespaces: varargs[Namespace]): Key =
Key(namespaces: self.namespaces & @namespaces)
func child*(self, key: Key): Key =
Key(namespaces: self.namespaces & key.namespaces)
func `/`*(self, key: Key): Key =
self.child(key)
func child*(self: Key, keys: varargs[Key]): Key =
Key(namespaces: self.namespaces & concat(keys.mapIt(it.namespaces)))
func child*(self: Key, ids: varargs[string]): ?!Key =
success self.child(ids.filterIt(it != "").mapIt( ?Key.init(it) ))
func relative*(self: Key, parent: Key): ?!Key =
## Get a key relative to parent from current key
##
if self.len < parent.len:
return failure "Not a parent of this key!"
Key.init(self.namespaces[parent.namespaces.high..self.namespaces.high])
func `/`*(self: Key, id: string): ?!Key =
self.child(id)
func ancestor*(self, other: Key): bool =
if other.len <= self.len: false
else: other.namespaces[0..<self.len] == self.namespaces
func descendant*(self, other: Key): bool =
other.ancestor(self)
func hash*(key: Key): Hash {.inline.} =
hash(key.id)
func `$`*(key: Key): string =
"Key(" & key.id & ")"
export key

134
datastore/key/key.nim Normal file
View File

@ -0,0 +1,134 @@
import std/algorithm
import std/hashes
import std/oids
import std/sequtils
import std/strutils
import std/strformat
import pkg/questionable
import pkg/questionable/results
from pkg/stew/results as stewResults import get, isErr
import pkg/upraises
import ./namespace
export hashes, namespace
push: {.upraises: [].}
type
Key* = object
namespaces*: seq[Namespace]
func init*(T: type Key, namespaces: varargs[Namespace]): ?!T =
success T(namespaces: @namespaces)
func init*(T: type Key, namespaces: varargs[string]): ?!T =
var self = T()
for s in namespaces:
self.namespaces &= s
.split( Separator )
.filterIt( it.len > 0 )
.mapIt( ?Namespace.init(it) )
return success self
func init*(T: type Key, keys: varargs[Key]): ?!T =
success T(
namespaces: keys
.mapIt(it.namespaces)
.concat)
func list*(self: Key): seq[Namespace] =
self.namespaces
proc random*(T: type Key): string =
$genOid()
template `[]`*(key: Key, x: auto): auto =
key.namespaces[x]
func len*(self: Key): int =
self.namespaces.len
iterator items*(key: Key): Namespace =
for k in key.namespaces:
yield k
func reverse*(self: Key): Key =
Key(namespaces: self.namespaces.reversed)
func value*(self: Key): string =
if self.len > 0:
return self[^1].value
func field*(self: Key): string =
if self.len > 0:
return self[^1].field
func id*(self: Key): string =
Separator & self.namespaces.mapIt(it.id).join(Separator)
func root*(self: Key): bool =
self.len == 1
func parent*(self: Key): ?!Key =
if self.root:
failure "key has no parent"
else:
success Key(namespaces: self.namespaces[0..^2])
func path*(self: Key): ?!Key =
let
tail =
if self[^1].field.len > 0:
self[^1].field
else:
self[^1].value
if self.root:
return Key.init(tail)
return success Key(
namespaces: (?self.parent).namespaces &
@[Namespace(value: tail)])
func child*(self: Key, namespaces: varargs[Namespace]): Key =
Key(namespaces: self.namespaces & @namespaces)
func `/`*(self: Key, ns: Namespace): Key =
self.child(ns)
func child*(self: Key, keys: varargs[Key]): Key =
Key(namespaces: self.namespaces & concat(keys.mapIt(it.namespaces)))
func `/`*(self, key: Key): Key =
self.child(key)
func child*(self: Key, ids: varargs[string]): ?!Key =
success self.child(ids.filterIt(it != "").mapIt( ?Key.init(it) ))
func `/`*(self: Key, id: string): ?!Key =
self.child(id)
func relative*(self: Key, parent: Key): ?!Key =
## Get a key relative to parent from current key
##
if self.len < parent.len:
return failure "Not a parent of this key!"
Key.init(self.namespaces[parent.namespaces.high..self.namespaces.high])
func ancestor*(self, other: Key): bool =
if other.len <= self.len: false
else: other.namespaces[0..<self.len] == self.namespaces
func descendant*(self, other: Key): bool =
other.ancestor(self)
func hash*(key: Key): Hash {.inline.} =
hash(key.id)
func `$`*(key: Key): string =
key.id

View File

@ -0,0 +1,69 @@
import std/hashes
import std/strformat
import pkg/questionable
import pkg/questionable/results
import pkg/upraises
push: {.upraises: [].}
const
Delimiter* = ":"
Separator* = "/"
type
Namespace* = object
field*: string
value*: string
func init*(T: type Namespace, field, value: string): ?!T =
if value.contains(Delimiter):
return failure (&"value string must not contain Delimiter '{Delimiter}'")
.catch.expect("should not fail")
if value.contains(Separator):
return failure (&"value string must not contain Separator {Separator}")
.catch.expect("should not fail")
if field.contains(Delimiter):
return failure (&"field string must not contain Delimiter {Delimiter}")
.catch.expect("should not fail")
if field.contains(Separator):
return failure (&"field string must not contain Separator {Separator}")
.catch.expect("should not fail")
success T(field: field, value: value)
func init*(T: type Namespace, id: string): ?!T =
if id.len > 0:
if id.contains(Separator):
return failure (&"id string must not contain Separator {Separator}")
.catch.expect("should not fail")
if id.count(Delimiter) > 1:
return failure (&"id string must not contain more than one {Delimiter}")
.catch.expect("should not fail")
let
(field, value) = block:
let parts = id.split(Delimiter)
if parts.len > 1:
(parts[0], parts[^1])
else:
("", parts[^1])
T.init(field.strip, value.strip)
func id*(self: Namespace): string =
if self.field.len > 0:
self.field & Delimiter & self.value
else:
self.value
func hash*(namespace: Namespace): Hash =
hash(namespace.id)
func `$`*(namespace: Namespace): string =
namespace.id

110
datastore/mountedds.nim Normal file
View File

@ -0,0 +1,110 @@
import std/tables
import pkg/chronos
import pkg/questionable
import pkg/questionable/results
import pkg/upraises
import ./key
import ./query
import ./datastore
export key, query
push: {.upraises: [].}
type
MountedStore* = object
store*: Datastore
key*: Key
MountedDatastore* = ref object of Datastore
stores*: Table[Key, MountedStore]
method mount*(self: MountedDatastore, key: Key, store: Datastore): ?!void {.base.} =
## Mount a store on a namespace - namespaces are only `/`
##
if key in self.stores:
return failure("Key already has store mounted!")
self.stores.add(key, MountedStore(store: store, key: key))
return success()
func findStore*(self: MountedDatastore, key: Key): ?!MountedStore =
## Find a store mounted under a particular key
##
for (k, v) in self.stores.pairs:
var
mounted = key
while mounted.len > 0:
if ?k.path == ?mounted.path:
return success v
if mounted.parent.isErr:
break
mounted = mounted.parent.get
failure newException(DatastoreKeyNotFound, "No datastore found for key")
proc dispatch(
self: MountedDatastore,
key: Key): ?!tuple[store: MountedStore, relative: Key] =
## Helper to retrieve the store and corresponding relative key
##
let
mounted = ?self.findStore(key)
return success (store: mounted, relative: ?key.relative(mounted.key))
method contains*(
self: MountedDatastore,
key: Key): Future[?!bool] {.async.} =
without mounted =? self.dispatch(key):
return failure "No mounted datastore found"
return (await mounted.store.store.contains(mounted.relative))
method delete*(
self: MountedDatastore,
key: Key): Future[?!void] {.async.} =
without mounted =? self.dispatch(key), error:
return failure(error)
return (await mounted.store.store.delete(mounted.relative))
method get*(
self: MountedDatastore,
key: Key): Future[?!seq[byte]] {.async.} =
without mounted =? self.dispatch(key), error:
return failure(error)
return await mounted.store.store.get(mounted.relative)
method put*(
self: MountedDatastore,
key: Key,
data: seq[byte]): Future[?!void] {.async.} =
without mounted =? self.dispatch(key), error:
return failure(error)
return (await mounted.store.store.put(mounted.relative, data))
func new*(
T: type MountedDatastore,
stores: Table[Key, Datastore] = initTable[Key, Datastore]()): ?!T =
var self = T()
for (k, v) in stores.pairs:
self.stores.add(?k.path, MountedStore(store: v, key: k))
success self

View File

@ -7,7 +7,7 @@ import pkg/sqlite3_abi
from pkg/stew/results as stewResults import isErr
import pkg/upraises
import ./datastore
import ../datastore
import ./sqlitedsdb
export datastore, sqlitedsdb

View File

@ -1,4 +1,6 @@
import std/options
import std/sequtils
import std/algorithm
import pkg/unittest2
import pkg/questionable
@ -7,112 +9,118 @@ import pkg/questionable/results
import ../../datastore/key
suite "Namespace":
test "init failure":
test "should fail init":
check:
Namespace.init("a", "").isFailure
Namespace.init("a", " ").isFailure
Namespace.init("a:b:c").isFailure
Namespace.init(":", "b").isFailure
Namespace.init("a", ":").isFailure
Namespace.init("a", "/").isFailure
Namespace.init(":", "b").isFailure
Namespace.init("/", "b").isFailure
Namespace.init(" ", "b").isFailure
Namespace.init("").isFailure
Namespace.init(" ").isFailure
Namespace.init("/").isFailure
Namespace.init(":").isFailure
Namespace.init("a:b:c").isFailure
Namespace.init("a:").isFailure
Namespace.init("a: ").isFailure
Namespace.init(" :b").isFailure
test "init success":
test "should succeed":
check:
Namespace.init(" :b").isSuccess
Namespace.init("a: ").isSuccess
Namespace.init("a", "").isSuccess
Namespace.init("a", " ").isSuccess
Namespace.init(" ", "b").isSuccess
Namespace.init("a:").isSuccess
Namespace.init("").isSuccess
Namespace.init(" ").isSuccess
Namespace.init(":").isSuccess
Namespace.init("", "b").isSuccess
Namespace.init("a", "b").isSuccess
Namespace.init("a").isSuccess
Namespace.init("a:b").isSuccess
Namespace.init(":b").isSuccess
test "accessors":
var
ns: Namespace
ns = Namespace.init("", "b").tryGet()
test "should init with value":
let
ns = Namespace.init("", "b").tryGet()
check:
ns.value == "b"
ns.field == ""
ns = Namespace.init("a", "b").tryGet()
check:
ns.value == "b"
ns.field != "" and ns.field == "a"
ns = Namespace.init(":b").tryGet()
check:
ns.value == "b"
ns.field == ""
ns = Namespace.init("a:b").tryGet()
test "should init with field and value":
let
ns = Namespace.init("a", "b").tryGet()
check:
ns.value == "b"
ns.field == "a"
test "equality":
test "should init with value from id string":
let
ns = Namespace.init(":b").tryGet()
check:
ns.value == "b"
ns.field == ""
test "should init with field and value from id string":
let
ns = Namespace.init("a:b").tryGet()
check:
ns.value == "b"
ns.field == "a"
test "should equal":
check:
Namespace.init("a").tryGet() == Namespace.init("a").tryGet()
Namespace.init("a").tryGet() != Namespace.init("b").tryGet()
Namespace.init("", "b").tryGet() == Namespace.init("", "b").tryGet()
Namespace.init("", "b").tryGet() == Namespace.init("b").tryGet()
Namespace.init(":b").tryGet() == Namespace.init("b").tryGet()
Namespace.init("", "b").tryGet() != Namespace.init("", "a").tryGet()
Namespace.init("", "b").tryGet() != Namespace.init("a").tryGet()
Namespace.init(":b").tryGet() != Namespace.init("a").tryGet()
Namespace.init("a", "b").tryGet() == Namespace.init("a", "b").tryGet()
Namespace.init("a", "b").tryGet() == Namespace.init("a:b").tryGet()
Namespace.init("a:b").tryGet() == Namespace.init("a:b").tryGet()
test "should not equal":
check:
Namespace.init("a").tryGet() != Namespace.init("b").tryGet()
Namespace.init("", "b").tryGet() != Namespace.init("", "a").tryGet()
Namespace.init("", "b").tryGet() != Namespace.init("a").tryGet()
Namespace.init(":b").tryGet() != Namespace.init("a").tryGet()
Namespace.init("a", "b").tryGet() != Namespace.init("b", "a").tryGet()
Namespace.init("a", "b").tryGet() != Namespace.init("b:a").tryGet()
Namespace.init("a:b").tryGet() != Namespace.init("b:a").tryGet()
Namespace.init("a").tryGet() != Namespace.init("a:b").tryGet()
test "serialization":
var
ns: Namespace
ns = Namespace.init(":b").tryGet()
test "should return id from value string":
let
ns = Namespace.init(":b").tryGet()
check:
ns.id == "b"
$ns == "Namespace(" & ns.id & ")"
$ns == ns.id
ns = Namespace.init("a:b").tryGet()
test "should init id from field and value string":
let
ns = Namespace.init("a:b").tryGet()
check:
ns.id == "a:b"
$ns == "Namespace(" & ns.id & ")"
$ns == ns.id
suite "Key":
test "init failure":
check:
Key.init("", "").isFailure
Key.init(@[""]).isFailure
Key.init(@[":"]).isFailure
Key.init(@["/"]).isFailure
Key.init("").isFailure
Key.init(" ").isFailure
Key.init("/").isFailure
Key.init("///").isFailure
Key.init(":").isFailure
Key.init("::").isFailure
Key.init("a:").isFailure
Key.init("a:b/c:").isFailure
test "init success":
check:
Key.init(@["/"]).isSuccess
Key.init("a:b/c:").isSuccess
Key.init("a:").isSuccess
Key.init(":").isSuccess
Key.init(@[":"]).isSuccess
Key.init("").isSuccess
Key.init(" ").isSuccess
Key.init("/").isSuccess
Key.init("///").isSuccess
Key.init(Namespace.init("a").tryGet()).isSuccess
Key.init(@["a:b"]).isSuccess
Key.init(":b").isSuccess
@ -135,20 +143,14 @@ suite "Key":
key.list == key.namespaces
test "equality":
test "should equal":
check:
Key.init(Namespace.init("a:b").tryGet(), Namespace.init("c").tryGet()).tryGet() == Key.init("a:b/c").tryGet()
Key.init("a:b", "c").tryGet() == Key.init("a:b/c").tryGet()
Key.init("a:b/c").tryGet() == Key.init("a:b/c").tryGet()
Key.init(Namespace.init("a:b").tryGet(), Namespace.init("c").tryGet()).tryGet() != Key.init("c:b/a").tryGet()
Key.init("a:b", "c").tryGet() != Key.init("c:b/a").tryGet()
Key.init("a:b/c").tryGet() != Key.init("c:b/a").tryGet()
Key.init("a:b/c").tryGet() == Key.init("/a:b/c/").tryGet()
Key.init("a:b/c").tryGet() == Key.init("///a:b///c///").tryGet()
Key.init("a:b/c").tryGet() != Key.init("///a:b///d///").tryGet()
Key.init("a").tryGet() != Key.init("a:b").tryGet()
Key.init("a").tryGet() != Key.init("a/b").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a/b").tryGet()
Key.init("a:X/b/c").tryGet() == Key.init("a:X/b/c").tryGet()
Key.init("a/b:X/c").tryGet() == Key.init("a/b:X/c").tryGet()
Key.init("a/b/c:X").tryGet() == Key.init("a/b/c:X").tryGet()
@ -156,6 +158,15 @@ suite "Key":
Key.init("a:X/b:X/c").tryGet() == Key.init("a:X/b:X/c").tryGet()
Key.init("a/b:X/c:X").tryGet() == Key.init("a/b:X/c:X").tryGet()
Key.init("a:X/b:X/c:X").tryGet() == Key.init("a:X/b:X/c:X").tryGet()
test "should not equal":
check:
Key.init("a:b", "c").tryGet() != Key.init("c:b/a").tryGet()
Key.init("a:b/c").tryGet() != Key.init("c:b/a").tryGet()
Key.init("a:b/c").tryGet() != Key.init("///a:b///d///").tryGet()
Key.init("a").tryGet() != Key.init("a:b").tryGet()
Key.init("a").tryGet() != Key.init("a/b").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a/b").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a:X/b/c").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a/b:X/c").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a/b/c:X").tryGet()
@ -164,9 +175,10 @@ suite "Key":
Key.init("a/b/c").tryGet() != Key.init("a/b:X/c:X").tryGet()
Key.init("a/b/c").tryGet() != Key.init("a:X/b:X/c:X").tryGet()
test "helpers":
test "random key":
check: Key.random.len == 24
test "key index":
let
key = Key.init("/a:b/c/d:e").tryGet()
@ -174,14 +186,14 @@ suite "Key":
key[1] == Namespace.init("c").tryGet()
key[1..^1] == @[Namespace.init("c").tryGet(), Namespace.init("d:e").tryGet()]
key[^1] == Namespace.init("d:e").tryGet()
key.len == key.namespaces.len
check: key.len == key.namespaces.len
test "key iterator":
let
key = Key.init("/a:b/c/d:e").tryGet()
var
nss: seq[Namespace]
for ns in key:
nss.add ns
nss = key.mapIt( it )
check:
nss == @[
@ -190,81 +202,107 @@ suite "Key":
Namespace.init("d:e").tryGet()
]
test "key reversed":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
key.reversed.namespaces == @[
key.reverse.namespaces == @[
Namespace.init("d:e").tryGet(),
Namespace.init("c").tryGet(),
Namespace.init("a:b").tryGet()
]
key.reverse == key.reversed
key.reverse.namespaces == key.namespaces.reversed
check: key.name == "e"
check:
key.reverse.value == "b"
key.reverse.field == "a"
test "key root":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
Key.init(":b").tryGet().root
not Key.init(":b/c").tryGet().root
test "key parent":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
Key.init(":b").?parent.isFailure
Key.init(":b").?parent.isFailure
key.parent.tryGet() == Key.init("a:b/c").tryGet()
key.parent.?parent.tryGet() == Key.init("a:b").tryGet()
key.parent.?parent.?parent.isFailure
test "key path":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
key.parent.?path.tryGet() == Key.init("a:b").tryGet()
key.path.tryGet() == Key.init("a:b/c/d").tryGet()
Key.init("a:b/c").?path.tryGet() == Key.init("a:b").tryGet()
key.path.tryGet() == Key.init("/a:b/c/d").tryGet()
key.parent.?path.tryGet() == Key.init("a:b/c").tryGet()
Key.init("a:b/c:d").?path.tryGet() == Key.init("a:b/c").tryGet()
Key.init("a:b/c/d:e").?path.tryGet() == Key.init("a:b/c/d").tryGet()
check: key.child(Namespace.init("f:g").tryGet()) == Key.init("a:b/c/d:e/f:g").tryGet()
check: key / Namespace.init("f:g").tryGet() == Key.init("a:b/c/d:e/f:g").tryGet()
var
emptyNss: seq[Namespace]
test "key child":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
key.child(emptyNss) == key
key.child(Namespace.init("f:g").tryGet(), Namespace.init("h:i").tryGet()) ==
Key.init("a:b/c/d:e/f:g/h:i").tryGet()
key.child(Namespace.init("f:g").tryGet()) == Key.init("a:b/c/d:e/f:g").tryGet()
key.child(newSeq[Namespace]()) == key
key.child(
Namespace.init("f:g").tryGet(),
Namespace.init("h:i").tryGet()) == Key.init("a:b/c/d:e/f:g/h:i").tryGet()
check:
key.child(Key.init("f:g").tryGet()) == Key.init("a:b/c/d:e/f:g").tryGet()
key / Key.init("f:g").tryGet() == Key.init("a:b/c/d:e/f:g").tryGet()
key.child(newSeq[Key]()) == key
var
emptyKeys: seq[Key]
key.child(
Key.init("f:g").tryGet(),
Key.init("h:i").tryGet()) == Key.init("a:b/c/d:e/f:g/h:i").tryGet()
check:
key.child(emptyKeys) == key
key.child(Key.init("f:g").tryGet(), Key.init("h:i").tryGet()) ==
Key.init("a:b/c/d:e/f:g/h:i").tryGet()
check:
key.child("f:g", ":::").isFailure
key.child("f:g", "h:i").tryGet() == Key.init("a:b/c/d:e/f:g/h:i").tryGet()
key.child("").tryGet() == key
key.child("", "", "").tryGet() == key
test "key / operator":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
key / Namespace.init("f:g").tryGet() == Key.init("a:b/c/d:e/f:g").tryGet()
(key / "").tryGet() == key
(key / "f:g").tryGet() == Key.init("a:b/c/d:e/f:g").tryGet()
test "key ancestor":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
not key.ancestor(Key.init("f:g").tryGet())
key.ancestor(key / Key.init("f:g").tryGet())
test "key descendant":
let
key = Key.init("/a:b/c/d:e").tryGet()
check:
key.descendant(key.parent.tryGet())
not Key.init("f:g").tryGet().descendant(key.parent.tryGet())
test "serialization":
test "key serialization":
let
idStr = "/a:b/c/d:e"
key = Key.init(idStr).tryGet()
check:
key.id == idStr
$key == "Key(" & key.id & ")"
$key == key.id

View File

@ -0,0 +1,143 @@
import std/options
import std/os
import std/tables
import pkg/asynctest
import pkg/chronos
import pkg/stew/results
import pkg/stew/byteutils
import pkg/datastore/mountedds
import pkg/datastore/sql
import pkg/datastore/fsds
import ./dscommontests
suite "Test Basic Mounted Datastore":
let
root = "tests" / "test_data"
(path, _, _) = instantiationInfo(-1, fullPaths = true) # get this file's name
rootAbs = path.parentDir / root
key = Key.init("a:b/c/d:e").get
sqlKey = Key.init("/root/sql").tryGet
fsKey = Key.init("/root/fs").tryGet
bytes = "some bytes".toBytes
otherBytes = "some other bytes".toBytes
var
sql: SQLiteDatastore
fs: FSDatastore
mountedDs: MountedDatastore
setupAll:
removeDir(rootAbs)
require(not dirExists(rootAbs))
createDir(rootAbs)
sql = SQLiteDatastore.new(Memory).tryGet
fs = FSDatastore.new(rootAbs, depth = 5).tryGet
mountedDs = MountedDatastore.new({
sqlKey: Datastore(sql),
fsKey: Datastore(fs)}.toTable)
.tryGet
teardownAll:
removeDir(rootAbs)
require(not dirExists(rootAbs))
suite "Mounted sql":
basicStoreTests(mountedDs, Key.init(sqlKey, key).tryGet, bytes, otherBytes)
suite "Mounted fs":
basicStoreTests(mountedDs, Key.init(fsKey, key).tryGet, bytes, otherBytes)
suite "Test Mounted Datastore":
test "Should mount datastore":
let
ds = SQLiteDatastore.new(Memory).tryGet
mounted = MountedDatastore.new().tryGet
key = Key.init("/sql").tryGet
mounted.mount(key, ds).tryGet
check: mounted.stores.len == 1
mounted.stores.withValue(key, store):
check:
store.key == key
store.store == ds
test "Should find with exact key":
let
ds = SQLiteDatastore.new(Memory).tryGet
key = Key.init("/sql").tryGet
mounted = MountedDatastore.new({key: Datastore(ds)}.toTable).tryGet
store = mounted.findStore(key).tryGet
check:
store.key == key
store.store == ds
test "Should find with child key":
let
ds = SQLiteDatastore.new(Memory).tryGet
key = Key.init("/sql").tryGet
childKey = Key.init("/sql/child/key").tryGet
mounted = MountedDatastore.new({key: Datastore(ds)}.toTable).tryGet
store = mounted.findStore(childKey).tryGet
check:
store.key == key
store.store == ds
test "Should error on missing key":
let
ds = SQLiteDatastore.new(Memory).tryGet
key = Key.init("/sql").tryGet
childKey = Key.init("/nomatchkey/child/key").tryGet
mounted = MountedDatastore.new({key: Datastore(ds)}.toTable).tryGet
expect DatastoreKeyNotFound:
discard mounted.findStore(childKey).tryGet
test "Should find nested stores":
let
ds1 = SQLiteDatastore.new(Memory).tryGet
ds2 = SQLiteDatastore.new(Memory).tryGet
key1 = Key.init("/sql").tryGet
key2 = Key.init("/sql/nested").tryGet
nestedKey1 = Key.init("/sql/anotherkey").tryGet
nestedKey2 = Key.init("/sql/nested/key").tryGet
mounted = MountedDatastore.new({
key1: Datastore(ds1),
key2: Datastore(ds2)}.toTable).tryGet
store1 = mounted.findStore(nestedKey1).tryGet
store2 = mounted.findStore(nestedKey2).tryGet
check:
store1.key == key1
store1.store == ds1
store2.key == key2
store2.store == ds2
test "Should find with field:value key":
let
ds = SQLiteDatastore.new(Memory).tryGet
key = Key.init("/sql").tryGet
findKey1 = Key.init("/sql:name1").tryGet
findKey2 = Key.init("/sql:name2").tryGet
mounted = MountedDatastore.new({key: Datastore(ds)}.toTable).tryGet
for k in @[findKey1, findKey2]:
let
store = mounted.findStore(k).tryGet
check:
store.key == key
store.store == ds

View File

@ -12,7 +12,7 @@ import pkg/datastore/tieredds
import ./dscommontests
suite "Test Basic FSDatastore":
suite "Test Basic Tired Datastore":
let
bytes = "some bytes".toBytes
otherBytes = "some other bytes".toBytes

View File

@ -3,6 +3,7 @@ import
./datastore/testdatastore,
./datastore/testfsds,
./datastore/testsql,
./datastore/testtieredds
./datastore/testtieredds,
./datastore/testmountedds
{.warning[UnusedImport]: off.}