nim-datastore/tests/datastore/test_sqlite_datastore.nim
Michael Bradley, Jr ca9ee12aeb check column metadata in id/data/timestampCol
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.
2022-07-15 10:56:45 -05:00

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