Add support for array[N, byte] in SQLite queries

This commit is contained in:
Mamy André-Ratsimbazafy 2020-11-27 22:27:14 +01:00 committed by zah
parent 49c40c9b95
commit 482ea988c0
2 changed files with 107 additions and 3 deletions

View File

@ -3,7 +3,7 @@
{.push raises: [Defect].} {.push raises: [Defect].}
import import
os, strformat, std/[os, strformat],
sqlite3_abi, sqlite3_abi,
./kvstore ./kvstore
@ -74,12 +74,22 @@ proc bindParam(s: RawStmtPtr, n: int, val: auto): cint =
sqlite3_bind_blob(s, n.cint, unsafeAddr val[0], val.len.cint, nil) sqlite3_bind_blob(s, n.cint, unsafeAddr val[0], val.len.cint, nil)
else: else:
sqlite3_bind_blob(s, n.cint, nil, 0.cint, nil) sqlite3_bind_blob(s, n.cint, nil, 0.cint, nil)
elif val is array:
when val.items.typeof is byte:
# Prior to Nim 1.4 and view types array[N, byte] in tuples
# don't match with openarray[byte]
if val.len > 0:
sqlite3_bind_blob(s, n.cint, unsafeAddr val[0], val.len.cint, nil)
else:
sqlite3_bind_blob(s, n.cint, nil, 0.cint, nil)
else:
{.fatal: "Please add support for the '" & $typeof(val) & "' type".}
elif val is int32: elif val is int32:
sqlite3_bind_int(s, n.cint, val) sqlite3_bind_int(s, n.cint, val)
elif val is int64: elif val is int64:
sqlite3_bind_int64(s, n.cint, val) sqlite3_bind_int64(s, n.cint, val)
else: else:
{.fatal: "Please add support for the '" & $(T) & "' type".} {.fatal: "Please add support for the '" & $typeof(val) & "' type".}
template bindParams(s: RawStmtPtr, params: auto) = template bindParams(s: RawStmtPtr, params: auto) =
when params is tuple: when params is tuple:
@ -125,6 +135,20 @@ template readResult(s: RawStmtPtr, column: cint, T: type): auto =
res.setLen(len) res.setLen(len)
copyMem(addr res[0], sqlite3_column_blob(s, column), len) copyMem(addr res[0], sqlite3_column_blob(s, column), len)
res res
elif T is array:
# array[N, byte]. "genericParams(T)[1]" requires 1.4 to handle nnkTypeOfExpr
when typeof(block: (var a: T; a[0])) is byte:
var res: T
let colLen = sqlite3_column_bytes(s, column)
# truncate if the type is too small
# TODO: warning/error? We assume that users always properly dimension buffers
let copyLen = min(colLen, res.len)
if copyLen > 0:
copyMem(addr res[0], sqlite3_column_blob(s, column), copyLen)
res
else:
{.fatal: "Please add support for the '" & $(T) & "' type".}
else: else:
{.fatal: "Please add support for the '" & $(T) & "' type".} {.fatal: "Please add support for the '" & $(T) & "' type".}
@ -445,4 +469,3 @@ when defined(metrics):
timestamp: timestamp, timestamp: timestamp,
), ),
] ]

View File

@ -95,3 +95,84 @@ procSuite "SqStoreRef":
@[byte 5] @[byte 5]
] ]
test "Tuple with byte arrays support":
# openarray[byte] requires either Nim 1.4
# 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
echo "About to call total attestations"
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