2020-04-27 13:16:11 +00:00
|
|
|
{.used.}
|
|
|
|
|
|
|
|
import
|
2022-06-02 12:14:15 +00:00
|
|
|
std/[os, options, sequtils],
|
2020-10-13 18:44:42 +00:00
|
|
|
testutils/unittests,
|
2022-06-02 12:14:15 +00:00
|
|
|
stew/endians2,
|
2020-04-27 13:16:11 +00:00
|
|
|
../../eth/db/[kvstore, kvstore_sqlite3],
|
|
|
|
./test_kvstore
|
|
|
|
|
2020-10-13 18:44:42 +00:00
|
|
|
procSuite "SqStoreRef":
|
2020-04-27 13:16:11 +00:00
|
|
|
test "KvStore interface":
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
|
|
|
defer: db.close()
|
2021-05-17 13:55:57 +00:00
|
|
|
let kv = db.openKvStore()
|
|
|
|
defer: kv.get()[].close()
|
2020-04-27 13:16:11 +00:00
|
|
|
|
2023-01-20 08:21:51 +00:00
|
|
|
testKvStore(kvStore kv.get(), true, true)
|
2020-10-13 18:44:42 +00:00
|
|
|
|
2022-11-28 20:15:53 +00:00
|
|
|
test "Readonly kvstore with no table":
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true, readOnly = true)[]
|
|
|
|
defer: db.close()
|
|
|
|
let kv = db.openKvStore().expect("working db")
|
|
|
|
|
|
|
|
check:
|
|
|
|
not kv.get([byte 0, 1, 2], nil).expect("ok to query data")
|
|
|
|
kv.find([byte 0, 1, 2], nil).expect("ok") == 0
|
|
|
|
kv.put([byte 0, 1, 2], []).isErr
|
|
|
|
kv.del([byte 0, 1, 2]).isOk
|
|
|
|
defer: kv[].close()
|
|
|
|
|
2020-10-13 18:44:42 +00:00
|
|
|
test "Prepare and execute statements":
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
|
|
|
defer: db.close()
|
|
|
|
|
|
|
|
let createTableRes = db.exec """
|
|
|
|
CREATE TABLE IF NOT EXISTS records(
|
|
|
|
key INTEGER PRIMARY KEY,
|
|
|
|
value BLOB
|
|
|
|
);
|
|
|
|
"""
|
|
|
|
check createTableRes.isOk
|
|
|
|
|
|
|
|
let insertStmt = db.prepareStmt(
|
|
|
|
"INSERT INTO records(value) VALUES (?);",
|
2021-12-20 12:14:50 +00:00
|
|
|
openArray[byte], void).get
|
2020-10-13 18:44:42 +00:00
|
|
|
|
|
|
|
let insert1Res = insertStmt.exec [byte 1, 2, 3, 4]
|
|
|
|
let insert2Res = insertStmt.exec @[]
|
|
|
|
let insert3Res = insertStmt.exec @[byte 5]
|
|
|
|
|
|
|
|
check:
|
|
|
|
insert1Res.isOk
|
|
|
|
insert2Res.isOk
|
|
|
|
insert3Res.isOk
|
|
|
|
|
|
|
|
let countStmt = db.prepareStmt(
|
|
|
|
"SELECT COUNT(*) FROM records;",
|
|
|
|
NoParams, int64).get
|
|
|
|
|
|
|
|
var totalRecords = 0
|
|
|
|
let countRes = countStmt.exec do (res: int64):
|
|
|
|
totalRecords = int res
|
|
|
|
|
|
|
|
check:
|
|
|
|
countRes.isOk and countRes.get == true
|
|
|
|
totalRecords == 3
|
|
|
|
|
2021-05-27 09:31:34 +00:00
|
|
|
# Without prepare..
|
|
|
|
totalRecords = 0
|
|
|
|
check:
|
|
|
|
(db.exec("SELECT COUNT(*) FROM records;", ()) do (res: int64):
|
|
|
|
totalRecords = int res).get()
|
|
|
|
|
|
|
|
check:
|
|
|
|
totalRecords == 3
|
|
|
|
|
2020-10-13 18:44:42 +00:00
|
|
|
let selectRangeStmt = db.prepareStmt(
|
|
|
|
"SELECT value FROM records WHERE key >= ? and key < ?;",
|
2021-12-20 12:14:50 +00:00
|
|
|
(int64, int64), openArray[byte]).get
|
2020-10-13 18:44:42 +00:00
|
|
|
|
|
|
|
block:
|
|
|
|
var allBytes = newSeq[byte]()
|
2021-12-20 12:14:50 +00:00
|
|
|
let selectRangeRes = selectRangeStmt.exec((0'i64, 5'i64)) do (bytes: openArray[byte]) {.gcsafe.}:
|
2020-10-13 18:44:42 +00:00
|
|
|
allBytes.add byte(bytes.len)
|
|
|
|
allBytes.add bytes
|
|
|
|
|
|
|
|
if selectRangeRes.isErr:
|
|
|
|
echo selectRangeRes.error
|
|
|
|
|
|
|
|
check:
|
|
|
|
selectRangeRes.isOk and selectRangeRes.get == true
|
|
|
|
allBytes == [byte 4, 1, 2, 3, 4,
|
|
|
|
0,
|
|
|
|
1, 5]
|
|
|
|
block:
|
2021-12-20 12:14:50 +00:00
|
|
|
let selectRangeRes = selectRangeStmt.exec((10'i64, 20'i64)) do (bytes: openArray[byte]):
|
2020-10-13 18:44:42 +00:00
|
|
|
echo "Got unexpected bytes: ", bytes
|
|
|
|
|
|
|
|
check:
|
|
|
|
selectRangeRes.isOk and selectRangeRes.get == false
|
|
|
|
|
|
|
|
let selectAllStmt = db.prepareStmt(
|
|
|
|
"SELECT * FROM records;",
|
|
|
|
NoParams, (int64, seq[byte])).get
|
|
|
|
|
|
|
|
var indices = newSeq[int64]()
|
|
|
|
var values = newSeq[seq[byte]]()
|
|
|
|
|
|
|
|
discard selectAllStmt.exec do (res: (int64, seq[byte])):
|
|
|
|
indices.add res[0]
|
|
|
|
values.add res[1]
|
|
|
|
|
|
|
|
check:
|
|
|
|
indices == [int64 1, 2, 3]
|
|
|
|
values == [
|
|
|
|
@[byte 1, 2, 3, 4],
|
|
|
|
@[],
|
|
|
|
@[byte 5]
|
|
|
|
]
|
|
|
|
|
2020-11-27 21:27:14 +00:00
|
|
|
test "Tuple with byte arrays support":
|
2021-12-20 12:14:50 +00:00
|
|
|
# openArray[byte] requires either Nim 1.4
|
2020-11-27 21:27:14 +00:00
|
|
|
# or hardcoding the seq[byte] and array[N, byte] paths
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
|
|
|
defer: db.close()
|
|
|
|
|
|
|
|
let createTableRes = db.exec """
|
|
|
|
CREATE TABLE IF NOT EXISTS attestations(
|
|
|
|
validator_id INTEGER NOT NULL,
|
|
|
|
source_epoch INTEGER NOT NULL,
|
|
|
|
target_epoch INTEGER NOT NULL,
|
|
|
|
attestation_root BLOB NOT NULL UNIQUE,
|
|
|
|
UNIQUE (validator_id, target_epoch)
|
|
|
|
);
|
|
|
|
"""
|
|
|
|
check createTableRes.isOk
|
|
|
|
|
|
|
|
let insertStmt = db.prepareStmt("""
|
|
|
|
INSERT INTO attestations(
|
|
|
|
validator_id,
|
|
|
|
source_epoch,
|
|
|
|
target_epoch,
|
|
|
|
attestation_root)
|
|
|
|
VALUES
|
|
|
|
(?,?,?,?);
|
|
|
|
""", (int32, int64, int64, array[32, byte]), void).get()
|
|
|
|
|
|
|
|
var hash: array[32, byte]
|
|
|
|
hash[1] = byte 1
|
|
|
|
hash[2] = byte 2
|
|
|
|
let insertRes = insertStmt.exec(
|
|
|
|
(123'i32, 2'i64, 4'i64, hash)
|
|
|
|
)
|
|
|
|
|
|
|
|
check: insertRes.isOk
|
|
|
|
|
|
|
|
let countStmt = db.prepareStmt(
|
|
|
|
"SELECT COUNT(*) FROM attestations;",
|
|
|
|
NoParams, int64).get
|
|
|
|
|
|
|
|
var totalRecords = 0
|
|
|
|
let countRes = countStmt.exec do (res: int64):
|
|
|
|
totalRecords = int res
|
|
|
|
|
|
|
|
check:
|
|
|
|
countRes.isOk and countRes.get == true
|
|
|
|
totalRecords == 1
|
|
|
|
|
|
|
|
let selectRangeStmt = db.prepareStmt("""
|
|
|
|
SELECT
|
|
|
|
source_epoch,
|
|
|
|
target_epoch,
|
|
|
|
attestation_root
|
|
|
|
FROM
|
|
|
|
attestations
|
|
|
|
WHERE
|
|
|
|
validator_id = ?
|
|
|
|
AND
|
|
|
|
? < source_epoch AND target_epoch < ?
|
|
|
|
LIMIT 1
|
|
|
|
""", (int32, int64, int64), (int64, int64, array[32, byte])).get()
|
|
|
|
|
|
|
|
block:
|
|
|
|
var digest: array[32, byte]
|
|
|
|
var source, target: int64
|
|
|
|
let selectRangeRes = selectRangeStmt.exec(
|
|
|
|
(123'i32, 1'i64, 5'i64)
|
|
|
|
) do (res: tuple[source, target: int64, hash: array[32, byte]]) {.gcsafe.}:
|
|
|
|
source = res.source
|
|
|
|
target = res.target
|
|
|
|
digest = res.hash
|
|
|
|
|
|
|
|
if selectRangeRes.isErr:
|
|
|
|
echo selectRangeRes.error
|
|
|
|
|
|
|
|
check:
|
|
|
|
selectRangeRes.isOk and selectRangeRes.get == true
|
|
|
|
source == 2
|
|
|
|
target == 4
|
|
|
|
digest == hash
|
2021-05-26 09:34:46 +00:00
|
|
|
|
|
|
|
test "null values":
|
|
|
|
#
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
|
|
|
defer: db.close()
|
|
|
|
|
|
|
|
let createTableRes = db.exec """
|
|
|
|
CREATE TABLE IF NOT EXISTS testnull(
|
|
|
|
a INTEGER PRIMARY KEY,
|
|
|
|
b INTEGER NULL,
|
|
|
|
c INTEGER NULL
|
|
|
|
);
|
|
|
|
"""
|
|
|
|
check createTableRes.isOk
|
|
|
|
|
|
|
|
type
|
|
|
|
ABC = (int64, Option[int64], Option[int64])
|
|
|
|
let insertStmt = db.prepareStmt("""
|
|
|
|
INSERT INTO testnull(a, b, c) VALUES (?,?,?);
|
|
|
|
""", ABC, void).get()
|
|
|
|
|
|
|
|
const val = (42'i64, none(int64), some(44'i64))
|
|
|
|
check:
|
|
|
|
insertStmt.exec(val).isOk()
|
|
|
|
|
|
|
|
let selectStmt = db.prepareStmt("""
|
|
|
|
SELECT a, b, c FROM testnull
|
|
|
|
""", NoParams, ABC).get()
|
|
|
|
|
|
|
|
block:
|
|
|
|
var abc: ABC
|
|
|
|
let selectRes = selectStmt.exec do (res: ABC):
|
|
|
|
abc = res
|
|
|
|
|
|
|
|
if selectRes.isErr:
|
|
|
|
echo selectRes.error
|
|
|
|
|
|
|
|
check:
|
|
|
|
selectRes.isOk and selectRes.get == true
|
|
|
|
abc == val
|
2022-01-28 13:23:41 +00:00
|
|
|
|
|
|
|
var found = false
|
|
|
|
var row: selectStmt.Result
|
|
|
|
for rowRes in selectStmt.exec(row):
|
|
|
|
rowRes.expect("working db")
|
|
|
|
check abc == row
|
|
|
|
found = true
|
2022-06-02 12:14:15 +00:00
|
|
|
check found
|
|
|
|
|
|
|
|
proc customSumFun(
|
|
|
|
a: openArray[byte],
|
2023-02-21 17:54:30 +00:00
|
|
|
b: openArray[byte]): Result[seq[byte], cstring] {.cdecl.} =
|
2022-06-02 12:14:15 +00:00
|
|
|
let num1 = uint32.fromBytesBE(a)
|
|
|
|
let num2 = uint32.fromBytesBE(b)
|
|
|
|
let sum = num1 + num2
|
|
|
|
let asBytes = sum.toBytesBE().toSeq()
|
|
|
|
return ok(asBytes)
|
|
|
|
|
|
|
|
test "Register custom scalar function":
|
|
|
|
let db = SqStoreRef.init("", "test", inMemory = true)[]
|
|
|
|
|
|
|
|
let registerResult = db.registerCustomScalarFunction("sum32", customSumFun)
|
|
|
|
|
|
|
|
check:
|
|
|
|
registerResult.isOk()
|
|
|
|
|
|
|
|
defer: db.close()
|
|
|
|
|
|
|
|
let kv = db.openKvStore().get()
|
|
|
|
defer: kv.close()
|
|
|
|
|
|
|
|
var sums: seq[seq[byte]] = @[]
|
|
|
|
|
2022-11-16 16:44:00 +00:00
|
|
|
# Use custom function, which interprets blobs as uint32 numbers and sums
|
2022-06-02 12:14:15 +00:00
|
|
|
# them together
|
|
|
|
let sumKeyVal = db.prepareStmt(
|
|
|
|
"SELECT sum32(key, value) FROM kvstore;",
|
|
|
|
NoParams, seq[byte]).get
|
|
|
|
|
|
|
|
let testUint = uint32(38)
|
|
|
|
|
|
|
|
let putRes = kv.put(testUint.toBytesBE(), testUint.toBytesBE())
|
|
|
|
|
|
|
|
check:
|
|
|
|
putRes.isOk()
|
|
|
|
|
|
|
|
discard sumKeyVal.exec do (res: seq[byte]):
|
|
|
|
sums.add(res)
|
2022-11-16 16:44:00 +00:00
|
|
|
|
2022-06-02 12:14:15 +00:00
|
|
|
check:
|
|
|
|
len(sums) == 1
|
2022-11-16 16:44:00 +00:00
|
|
|
|
2022-06-02 12:14:15 +00:00
|
|
|
let sum = uint32.fromBytesBE(sums[0])
|
|
|
|
|
|
|
|
check:
|
|
|
|
sum == testUint + testUint
|