From 68e6aadc29fa2f5d52341cefa7b0779ae9dc240a Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Wed, 26 May 2021 11:34:46 +0200 Subject: [PATCH] sqlite3: support option type .. and any integer when writing but don't support int32 on reads - internally sqlite will truncate on overflow which isn't nice. --- eth/db/kvstore_sqlite3.nim | 34 +++++++++++++++++------- tests/db/test_kvstore_sqlite3.nim | 44 ++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/eth/db/kvstore_sqlite3.nim b/eth/db/kvstore_sqlite3.nim index c73774b..0e63904 100644 --- a/eth/db/kvstore_sqlite3.nim +++ b/eth/db/kvstore_sqlite3.nim @@ -93,10 +93,13 @@ proc bindParam(s: RawStmtPtr, n: int, val: auto): cint = sqlite3_bind_blob(s, n.cint, nil, 0.cint, nil) else: {.fatal: "Please add support for the '" & $typeof(val) & "' type".} - elif val is int32: - sqlite3_bind_int(s, n.cint, val) - elif val is int64: - sqlite3_bind_int64(s, n.cint, val) + elif val is SomeInteger: + sqlite3_bind_int64(s, n.cint, val.clong) + elif val is Option: + if val.isNone(): + sqlite3_bind_null(s, n.cint) + else: + bindParam(s, n, val.get()) else: {.fatal: "Please add support for the '" & $typeof(val) & "' type".} @@ -125,13 +128,15 @@ proc exec*[P](s: SqliteStmt[P, void], params: P): KvResult[void] = res -template readResult(s: RawStmtPtr, column: cint, T: type): auto = - when T is int32: - sqlite3_column_int(s, column) - elif T is int64: +template readSimpleResult(s: RawStmtPtr, column: cint, T: type): auto = + when T is int64: sqlite3_column_int64(s, column) - elif T is int: - {.fatal: "Please use specify either int32 or int64 precisely".} + elif T is SomeInteger: + # sqlite integers are "up to" 8 bytes in size, so rather than silently + # truncate them, we support only 64-bit integers when reading and let the + # calling code deal with it - careful though, anything that is not an + # integer (ie TEXT) is returned as 0 + {.fatal: "Use int64 for reading integers".} elif T is openArray[byte]: let p = cast[ptr UncheckedArray[byte]](sqlite3_column_blob(s, column)) @@ -161,6 +166,15 @@ template readResult(s: RawStmtPtr, column: cint, T: type): auto = else: {.fatal: "Please add support for the '" & $(T) & "' type".} +template readResult(s: RawStmtPtr, column: cint, T: type): auto = + when T is Option: + if sqlite3_column_type(s, column) == SQLITE_NULL: + none(typeof(default(T).get())) + else: + some(readSimpleResult(s, column, typeof(default(T).get()))) + else: + readSimpleResult(s, column, T) + template readResult(s: RawStmtPtr, T: type): auto = when T is tuple: var res: T diff --git a/tests/db/test_kvstore_sqlite3.nim b/tests/db/test_kvstore_sqlite3.nim index 29a73b6..9694f2f 100644 --- a/tests/db/test_kvstore_sqlite3.nim +++ b/tests/db/test_kvstore_sqlite3.nim @@ -1,7 +1,7 @@ {.used.} import - std/os, + std/[os, options], testutils/unittests, ../../eth/db/[kvstore, kvstore_sqlite3], ./test_kvstore @@ -45,7 +45,6 @@ procSuite "SqStoreRef": NoParams, int64).get var totalRecords = 0 - echo "About to call total records" let countRes = countStmt.exec do (res: int64): totalRecords = int res @@ -138,7 +137,6 @@ procSuite "SqStoreRef": NoParams, int64).get var totalRecords = 0 - echo "About to call total attestations" let countRes = countStmt.exec do (res: int64): totalRecords = int res @@ -178,3 +176,43 @@ procSuite "SqStoreRef": source == 2 target == 4 digest == hash + + 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