mirror of
https://github.com/logos-storage/nim-datastore.git
synced 2026-01-06 15:43:10 +00:00
The goal is to detect mismatches between caller-supplied indexes and original column names, and in that case crash the process by raising Defect. This should help avoid harder to debug errors for such mismatches. These helper procs are now higher-order, which allows column metadata checks to be run only once, i.e. when setting up derivative helpers to be used in an `onData` callback. Use compile-time constants for column names and default indexes. Adjust callback proc annotations to be more precise, and remove unnecessary annotations.
337 lines
6.7 KiB
Nim
337 lines
6.7 KiB
Nim
import std/options
|
|
import std/os
|
|
|
|
import pkg/asynctest/unittest2
|
|
import pkg/chronos
|
|
import pkg/stew/results
|
|
|
|
import ../../datastore/sqlite_datastore
|
|
import ./templates
|
|
|
|
suite "SQLiteDatastore":
|
|
var
|
|
ds: SQLiteDatastore
|
|
|
|
# assumes tests/test_all is run from project root, e.g. with `nimble test`
|
|
let
|
|
basePath = "tests" / "test_data"
|
|
basePathAbs = getCurrentDir() / basePath
|
|
filename = "test_store" & dbExt
|
|
dbPathAbs = basePathAbs / filename
|
|
|
|
setup:
|
|
removeDir(basePathAbs)
|
|
require(not dirExists(basePathAbs))
|
|
|
|
teardown:
|
|
if not ds.isNil: ds.close
|
|
ds = nil
|
|
removeDir(basePathAbs)
|
|
require(not dirExists(basePathAbs))
|
|
|
|
asyncTest "new":
|
|
var
|
|
dsRes = SQLiteDatastore.new(basePathAbs, filename, readOnly = true)
|
|
|
|
# for `readOnly = true` to succeed the database file must already exist
|
|
check: dsRes.isErr
|
|
|
|
dsRes = SQLiteDatastore.new(basePathAbs, filename)
|
|
|
|
assert dsRes.isOk
|
|
ds = dsRes.get
|
|
|
|
check:
|
|
dirExists(basePathAbs)
|
|
fileExists(dbPathAbs)
|
|
|
|
ds.close
|
|
removeDir(basePathAbs)
|
|
assert not dirExists(basePathAbs)
|
|
|
|
dsRes = SQLiteDatastore.new(basePath, filename)
|
|
|
|
assert dsRes.isOk
|
|
ds = dsRes.get
|
|
|
|
check:
|
|
dirExists(basePathAbs)
|
|
fileExists(dbPathAbs)
|
|
|
|
ds.close
|
|
|
|
# for `readOnly = true` to succeed the database file must already exist, so
|
|
# the existing file (per previous step) is not deleted prior to the next
|
|
# invocation of `SQLiteDatastore.new`
|
|
|
|
dsRes = SQLiteDatastore.new(basePath, filename, readOnly = true)
|
|
|
|
assert dsRes.isOk
|
|
ds = dsRes.get
|
|
|
|
check:
|
|
dirExists(basePathAbs)
|
|
fileExists(dbPathAbs)
|
|
|
|
ds.close
|
|
removeDir(basePathAbs)
|
|
assert not dirExists(basePathAbs)
|
|
|
|
dsRes = SQLiteDatastore.new(inMemory = true)
|
|
|
|
assert dsRes.isOk
|
|
ds = dsRes.get
|
|
|
|
check:
|
|
not dirExists(basePathAbs)
|
|
not fileExists(dbPathAbs)
|
|
|
|
ds.close
|
|
|
|
dsRes = SQLiteDatastore.new(readOnly = true, inMemory = true)
|
|
|
|
check: dsRes.isErr
|
|
|
|
asyncTest "accessors":
|
|
ds = SQLiteDatastore.new(basePath).get
|
|
|
|
check:
|
|
parentDir(ds.dbPath) == basePathAbs
|
|
not ds.env.isNil
|
|
|
|
asyncTest "helpers":
|
|
ds = SQLiteDatastore.new(basePath).get
|
|
|
|
ds.close
|
|
|
|
check:
|
|
ds.env.isNil
|
|
timestamp(10.123_456) == 10_123_456.int64
|
|
|
|
asyncTest "put":
|
|
let
|
|
key = Key.init("a:b/c/d:e").get
|
|
|
|
# for `readOnly = true` to succeed the database file must already exist
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
ds.close
|
|
ds = SQLiteDatastore.new(basePathAbs, filename, readOnly = true).get
|
|
|
|
var
|
|
bytes: seq[byte]
|
|
timestamp = timestamp()
|
|
putRes = await ds.put(key, bytes, timestamp)
|
|
|
|
check: putRes.isErr
|
|
|
|
ds.close
|
|
removeDir(basePathAbs)
|
|
assert not dirExists(basePathAbs)
|
|
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
|
|
timestamp = timestamp()
|
|
putRes = await ds.put(key, bytes, timestamp)
|
|
|
|
check: putRes.isOk
|
|
|
|
let
|
|
prequeryRes = NoParamsStmt.prepare(
|
|
ds.env, "SELECT timestamp as foo, id as baz, data as bar FROM " &
|
|
tableName & ";")
|
|
|
|
assert prequeryRes.isOk
|
|
|
|
let
|
|
prequery = prequeryRes.get
|
|
idCol = idCol(RawStmtPtr(prequery), 1)
|
|
dataCol = dataCol(RawStmtPtr(prequery), 2)
|
|
timestampCol = timestampCol(RawStmtPtr(prequery), 0)
|
|
|
|
var
|
|
qId: string
|
|
qData: seq[byte]
|
|
qTimestamp: int64
|
|
rowCount = 0
|
|
|
|
proc onData(s: RawStmtPtr) {.closure.} =
|
|
qId = idCol()
|
|
qData = dataCol()
|
|
qTimestamp = timestampCol()
|
|
inc rowCount
|
|
|
|
var
|
|
qRes = prequery.query((), onData)
|
|
|
|
assert qRes.isOk
|
|
|
|
check:
|
|
qRes.get
|
|
qId == key.id
|
|
qData == bytes
|
|
qTimestamp == timestamp
|
|
rowCount == 1
|
|
|
|
bytes = @[1.byte, 2.byte, 3.byte]
|
|
timestamp = timestamp()
|
|
putRes = await ds.put(key, bytes, timestamp)
|
|
|
|
check: putRes.isOk
|
|
|
|
rowCount = 0
|
|
qRes = prequery.query((), onData)
|
|
assert qRes.isOk
|
|
|
|
check:
|
|
qRes.get
|
|
qId == key.id
|
|
qData == bytes
|
|
qTimestamp == timestamp
|
|
rowCount == 1
|
|
|
|
bytes = @[4.byte, 5.byte, 6.byte]
|
|
timestamp = timestamp()
|
|
putRes = await ds.put(key, bytes, timestamp)
|
|
|
|
check: putRes.isOk
|
|
|
|
rowCount = 0
|
|
qRes = prequery.query((), onData)
|
|
assert qRes.isOk
|
|
|
|
check:
|
|
qRes.get
|
|
qId == key.id
|
|
qData == bytes
|
|
qTimestamp == timestamp
|
|
rowCount == 1
|
|
|
|
prequery.dispose
|
|
|
|
asyncTest "delete":
|
|
let
|
|
bytes = @[1.byte, 2.byte, 3.byte]
|
|
|
|
var
|
|
key = Key.init("a:b/c/d:e").get
|
|
|
|
# for `readOnly = true` to succeed the database file must already exist
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
ds.close
|
|
ds = SQLiteDatastore.new(basePathAbs, filename, readOnly = true).get
|
|
|
|
var
|
|
delRes = await ds.delete(key)
|
|
|
|
check: delRes.isErr
|
|
|
|
ds.close
|
|
removeDir(basePathAbs)
|
|
assert not dirExists(basePathAbs)
|
|
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
|
|
let
|
|
putRes = await ds.put(key, bytes)
|
|
|
|
assert putRes.isOk
|
|
|
|
let
|
|
query = "SELECT * FROM " & tableName & ";"
|
|
|
|
var
|
|
rowCount = 0
|
|
|
|
proc onData(s: RawStmtPtr) {.closure.} =
|
|
inc rowCount
|
|
|
|
var
|
|
qRes = ds.env.query(query, onData)
|
|
|
|
assert qRes.isOk
|
|
check: rowCount == 1
|
|
delRes = await ds.delete(key)
|
|
|
|
check: delRes.isOk
|
|
|
|
rowCount = 0
|
|
qRes = ds.env.query(query, onData)
|
|
assert qRes.isOk
|
|
|
|
check:
|
|
delRes.isOk
|
|
rowCount == 0
|
|
|
|
key = Key.init("X/Y/Z").get
|
|
|
|
delRes = await ds.delete(key)
|
|
|
|
check: delRes.isOk
|
|
|
|
asyncTest "contains":
|
|
let
|
|
bytes = @[1.byte, 2.byte, 3.byte]
|
|
|
|
var
|
|
key = Key.init("a:b/c/d:e").get
|
|
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
|
|
let
|
|
putRes = await ds.put(key, bytes)
|
|
|
|
assert putRes.isOk
|
|
|
|
var
|
|
containsRes = await ds.contains(key)
|
|
|
|
assert containsRes.isOk
|
|
|
|
check: containsRes.get == true
|
|
|
|
key = Key.init("X/Y/Z").get
|
|
|
|
containsRes = await ds.contains(key)
|
|
assert containsRes.isOk
|
|
|
|
check: containsRes.get == false
|
|
|
|
asyncTest "get":
|
|
ds = SQLiteDatastore.new(basePathAbs, filename).get
|
|
|
|
var
|
|
bytes: seq[byte]
|
|
key = Key.init("a:b/c/d:e").get
|
|
putRes = await ds.put(key, bytes)
|
|
|
|
assert putRes.isOk
|
|
|
|
var
|
|
getRes = await ds.get(key)
|
|
getOpt = getRes.get
|
|
|
|
check: getOpt.isSome and getOpt.get == bytes
|
|
|
|
bytes = @[1.byte, 2.byte, 3.byte]
|
|
putRes = await ds.put(key, bytes)
|
|
|
|
assert putRes.isOk
|
|
|
|
getRes = await ds.get(key)
|
|
getOpt = getRes.get
|
|
|
|
check: getOpt.isSome and getOpt.get == bytes
|
|
|
|
key = Key.init("X/Y/Z").get
|
|
|
|
assert not (await ds.contains(key)).get
|
|
|
|
getRes = await ds.get(key)
|
|
getOpt = getRes.get
|
|
|
|
check: getOpt.isNone
|
|
|
|
# asyncTest "query":
|
|
# check:
|
|
# true
|