diff --git a/datastore.nim b/datastore.nim index 6e32f08..6d43a20 100644 --- a/datastore.nim +++ b/datastore.nim @@ -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 diff --git a/datastore/datastore.nim b/datastore/datastore.nim index 4bb44b0..2c027d5 100644 --- a/datastore/datastore.nim +++ b/datastore/datastore.nim @@ -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!") diff --git a/datastore/key.nim b/datastore/key.nim index b9731dc..941adc8 100644 --- a/datastore/key.nim +++ b/datastore/key.nim @@ -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.. 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.. 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 diff --git a/datastore/mountedds.nim b/datastore/mountedds.nim new file mode 100644 index 0000000..c502731 --- /dev/null +++ b/datastore/mountedds.nim @@ -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 diff --git a/datastore/sql/sqliteds.nim b/datastore/sql/sqliteds.nim index a1a8bfa..aae12e5 100644 --- a/datastore/sql/sqliteds.nim +++ b/datastore/sql/sqliteds.nim @@ -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 diff --git a/tests/datastore/testkey.nim b/tests/datastore/testkey.nim index 9223b7b..d5fd2fd 100644 --- a/tests/datastore/testkey.nim +++ b/tests/datastore/testkey.nim @@ -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 diff --git a/tests/datastore/testmountedds.nim b/tests/datastore/testmountedds.nim new file mode 100644 index 0000000..421f786 --- /dev/null +++ b/tests/datastore/testmountedds.nim @@ -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 diff --git a/tests/datastore/testtieredds.nim b/tests/datastore/testtieredds.nim index 86222ae..956fe3a 100644 --- a/tests/datastore/testtieredds.nim +++ b/tests/datastore/testtieredds.nim @@ -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 diff --git a/tests/testall.nim b/tests/testall.nim index 80f20b2..a6aca01 100644 --- a/tests/testall.nim +++ b/tests/testall.nim @@ -3,6 +3,7 @@ import ./datastore/testdatastore, ./datastore/testfsds, ./datastore/testsql, - ./datastore/testtieredds + ./datastore/testtieredds, + ./datastore/testmountedds {.warning[UnusedImport]: off.}