Handle BLOB column type in SQLite as binary data (#15681)

* Fixed not handling blob correctly in sqlite
* Fixed setLen commented by mistake
* Added binary example as db_sqlite doc
* Added tests for sqlite binary data
This commit is contained in:
Regis Caillaud 2020-11-02 13:02:55 +01:00 • committed by GitHub
commit 6fa82a5b3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 15 deletions

View file

@ -94,6 +94,57 @@
## ##
## db.close() ## db.close()
## ##
## Storing binary data example
##----------------------------
##
## .. code-block:: nim
##
## import random
##
## ## Generate random float datas
## var orig = newSeq[float64](150)
## randomize()
## for x in orig.mitems:
## x = rand(1.0)/10.0
##
## let db = open("mysqlite.db", "", "", "")
## block: ## Create database
## ## Binary datas needs to be of type BLOB in SQLite
## let createTableStr = sql"""CREATE TABLE test(
## id INTEGER NOT NULL PRIMARY KEY,
## data BLOB
## )
## """
## db.exec(createTableStr)
##
## block: ## Insert data
## var id = 1
## ## Data needs to be converted to seq[byte] to be interpreted as binary by bindParams
## var dbuf = newSeq[byte](orig.len*sizeof(float64))
## copyMem(unsafeAddr(dbuf[0]), unsafeAddr(orig[0]), dbuf.len)
##
## ## Use prepared statement to insert binary data into database
## var insertStmt = db.prepare("INSERT INTO test (id, data) VALUES (?, ?)")
## insertStmt.bindParams(id, dbuf)
## let bres = db.tryExec(insertStmt)
## ## Check insert
## doAssert(bres)
## # Destroy statement
## finalize(insertStmt)
##
## block: ## Use getValue to select data
## var dataTest = db.getValue(sql"SELECT data FROM test WHERE id = ?", 1)
## ## Calculate sequence size from buffer size
## let seqSize = int(dataTest.len*sizeof(byte)/sizeof(float64))
## ## Copy binary string data in dataTest into a seq
## var res: seq[float64] = newSeq[float64](seqSize)
## copyMem(unsafeAddr(res[0]), addr(dataTest[0]), dataTest.len)
##
## ## Check datas obtained is identical
## doAssert res == orig
##
## db.close()
##
## ##
## Note ## Note
## ==== ## ====
@ -242,10 +293,14 @@ proc setupQuery(db: DbConn, stmtName: SqlPrepared): SqlPrepared {.since: (1, 3).
proc setRow(stmt: PStmt, r: var Row, cols: cint) = proc setRow(stmt: PStmt, r: var Row, cols: cint) =
for col in 0'i32..cols-1: for col in 0'i32..cols-1:
setLen(r[col], column_bytes(stmt, col)) # set capacity let cb = column_bytes(stmt, col)
setLen(r[col], 0) setLen(r[col], cb) # set capacity
let x = column_text(stmt, col) if column_type(stmt, col) == SQLITE_BLOB:
if not isNil(x): add(r[col], x) copyMem(addr(r[col][0]), column_blob(stmt, col), cb)
else:
setLen(r[col], 0)
let x = column_text(stmt, col)
if not isNil(x): add(r[col], x)
iterator fastRows*(db: DbConn, query: SqlQuery, iterator fastRows*(db: DbConn, query: SqlQuery,
args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} = args: varargs[string, `$`]): Row {.tags: [ReadDbEffect].} =
@ -558,8 +613,12 @@ proc getValue*(db: DbConn, query: SqlQuery,
if cb == 0: if cb == 0:
result = "" result = ""
else: else:
result = newStringOfCap(cb) if column_type(stmt, 0) == SQLITE_BLOB:
add(result, column_text(stmt, 0)) result.setLen(cb)
copyMem(addr(result[0]), column_blob(stmt, 0), cb)
else:
result = newStringOfCap(cb)
add(result, column_text(stmt, 0))
else: else:
result = "" result = ""
if finalize(stmt) != SQLITE_OK: dbError(db) if finalize(stmt) != SQLITE_OK: dbError(db)
@ -572,8 +631,12 @@ proc getValue*(db: DbConn, stmtName: SqlPrepared): string
if cb == 0: if cb == 0:
result = "" result = ""
else: else:
result = newStringOfCap(cb) if column_type(stmt, 0) == SQLITE_BLOB:
add(result, column_text(stmt, 0)) result.setLen(cb)
copyMem(addr(result[0]), column_blob(stmt, 0), cb)
else:
result = newStringOfCap(cb)
add(result, column_text(stmt, 0))
else: else:
result = "" result = ""

View file

@ -0,0 +1,50 @@
discard """
action: "run"
exitcode: 0
"""
import db_sqlite
import random
import os
from stdtest/specialpaths import buildDir
block tsqlitebindatas: ## db_sqlite binary data
const dbName = buildDir / "tsqlitebindatas.db"
let origName = "Bobby"
var orig = newSeq[float64](150)
randomize()
for x in orig.mitems:
x = rand(1.0)/10.0
discard tryRemoveFile(dbName)
let db = open(dbName, "", "", "")
let createTableStr = sql"""CREATE TABLE test(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT,
data BLOB
)
"""
db.exec(createTableStr)
var dbuf = newSeq[byte](orig.len*sizeof(float64))
copyMem(unsafeAddr(dbuf[0]), unsafeAddr(orig[0]), dbuf.len)
var insertStmt = db.prepare("INSERT INTO test (id, name, data) VALUES (?, ?, ?)")
insertStmt.bindParams(1, origName, dbuf)
let bres = db.tryExec(insertStmt)
doAssert(bres)
finalize(insertStmt)
var nameTest = db.getValue(sql"SELECT name FROM test WHERE id = ?", 1)
doAssert nameTest == origName
var dataTest = db.getValue(sql"SELECT data FROM test WHERE id = ?", 1)
let seqSize = int(dataTest.len*sizeof(byte)/sizeof(float64))
var res: seq[float64] = newSeq[float64](seqSize)
copyMem(unsafeAddr(res[0]), addr(dataTest[0]), dataTest.len)
doAssert res.len == orig.len
doAssert res == orig
db.close()
doAssert tryRemoveFile(dbName)