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:
parent
4fe56b56ce
commit
6fa82a5b3a
2 changed files with 128 additions and 15 deletions
|
|
@ -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].} =
|
||||||
|
|
@ -288,7 +343,7 @@ iterator fastRows*(db: DbConn, query: SqlQuery,
|
||||||
finally:
|
finally:
|
||||||
if finalize(stmt) != SQLITE_OK: dbError(db)
|
if finalize(stmt) != SQLITE_OK: dbError(db)
|
||||||
|
|
||||||
iterator fastRows*(db: DbConn, stmtName: SqlPrepared): Row
|
iterator fastRows*(db: DbConn, stmtName: SqlPrepared): Row
|
||||||
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
||||||
discard setupQuery(db, stmtName)
|
discard setupQuery(db, stmtName)
|
||||||
var L = (column_count(stmtName.PStmt))
|
var L = (column_count(stmtName.PStmt))
|
||||||
|
|
@ -491,7 +546,7 @@ proc getAllRows*(db: DbConn, query: SqlQuery,
|
||||||
for r in fastRows(db, query, args):
|
for r in fastRows(db, query, args):
|
||||||
result.add(r)
|
result.add(r)
|
||||||
|
|
||||||
proc getAllRows*(db: DbConn, stmtName: SqlPrepared): seq[Row]
|
proc getAllRows*(db: DbConn, stmtName: SqlPrepared): seq[Row]
|
||||||
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
||||||
result = @[]
|
result = @[]
|
||||||
for r in fastRows(db, stmtName):
|
for r in fastRows(db, stmtName):
|
||||||
|
|
@ -524,7 +579,7 @@ iterator rows*(db: DbConn, query: SqlQuery,
|
||||||
## db.close()
|
## db.close()
|
||||||
for r in fastRows(db, query, args): yield r
|
for r in fastRows(db, query, args): yield r
|
||||||
|
|
||||||
iterator rows*(db: DbConn, stmtName: SqlPrepared): Row
|
iterator rows*(db: DbConn, stmtName: SqlPrepared): Row
|
||||||
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
||||||
for r in fastRows(db, stmtName): yield r
|
for r in fastRows(db, stmtName): yield r
|
||||||
|
|
||||||
|
|
@ -558,13 +613,17 @@ 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)
|
||||||
|
|
||||||
proc getValue*(db: DbConn, stmtName: SqlPrepared): string
|
proc getValue*(db: DbConn, stmtName: SqlPrepared): string
|
||||||
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
{.tags: [ReadDbEffect,WriteDbEffect], since: (1, 3).} =
|
||||||
var stmt = setupQuery(db, stmtName).PStmt
|
var stmt = setupQuery(db, stmtName).PStmt
|
||||||
if step(stmt) == SQLITE_ROW:
|
if step(stmt) == SQLITE_ROW:
|
||||||
|
|
@ -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 = ""
|
||||||
|
|
||||||
|
|
@ -671,7 +734,7 @@ proc execAffectedRows*(db: DbConn, query: SqlQuery,
|
||||||
exec(db, query, args)
|
exec(db, query, args)
|
||||||
result = changes(db)
|
result = changes(db)
|
||||||
|
|
||||||
proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared): int64
|
proc execAffectedRows*(db: DbConn, stmtName: SqlPrepared): int64
|
||||||
{.tags: [ReadDbEffect, WriteDbEffect],since: (1, 3).} =
|
{.tags: [ReadDbEffect, WriteDbEffect],since: (1, 3).} =
|
||||||
exec(db, stmtName)
|
exec(db, stmtName)
|
||||||
result = changes(db)
|
result = changes(db)
|
||||||
|
|
@ -723,7 +786,7 @@ proc setEncoding*(connection: DbConn, encoding: string): bool {.
|
||||||
exec(connection, sql"PRAGMA encoding = ?", [encoding])
|
exec(connection, sql"PRAGMA encoding = ?", [encoding])
|
||||||
result = connection.getValue(sql"PRAGMA encoding") == encoding
|
result = connection.getValue(sql"PRAGMA encoding") == encoding
|
||||||
|
|
||||||
proc finalize*(sqlPrepared:SqlPrepared) {.discardable, since: (1, 3).} =
|
proc finalize*(sqlPrepared:SqlPrepared) {.discardable, since: (1, 3).} =
|
||||||
discard finalize(sqlPrepared.PStmt)
|
discard finalize(sqlPrepared.PStmt)
|
||||||
|
|
||||||
template dbBindParamError*(paramIdx: int, val: varargs[untyped]) =
|
template dbBindParamError*(paramIdx: int, val: varargs[untyped]) =
|
||||||
|
|
@ -756,7 +819,7 @@ proc bindParam*(ps: SqlPrepared, paramIdx: int, val: float64) {.since: (1, 3).}
|
||||||
dbBindParamError(paramIdx, val)
|
dbBindParamError(paramIdx, val)
|
||||||
|
|
||||||
proc bindNull*(ps: SqlPrepared, paramIdx: int) {.since: (1, 3).} =
|
proc bindNull*(ps: SqlPrepared, paramIdx: int) {.since: (1, 3).} =
|
||||||
## Sets the bindparam at the specified paramIndex to null
|
## Sets the bindparam at the specified paramIndex to null
|
||||||
## (default behaviour by sqlite).
|
## (default behaviour by sqlite).
|
||||||
if bind_null(ps.PStmt, paramIdx.int32) != SQLITE_OK:
|
if bind_null(ps.PStmt, paramIdx.int32) != SQLITE_OK:
|
||||||
dbBindParamError(paramIdx)
|
dbBindParamError(paramIdx)
|
||||||
|
|
|
||||||
50
tests/stdlib/tsqlitebindatas.nim
Normal file
50
tests/stdlib/tsqlitebindatas.nim
Normal 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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue