Hi. I am a newbie and wonder if manipulating strings by their pointers like below is safe or does it leak memory or something ?
import tables
var
vars : Table[string,ptr string] = initTable[string,ptr string]()
proc declare(name:string,value: var string) =
vars.add(name,addr value)
proc setPerson(name,surname: string) =
vars.mget("name")[] =name
vars.mget("surname")[] = surname
var
name : string = ""
surname : string = ""
declare("name",name)
declare("surname",surname)
setPerson("John","Doe")
echo("Hello ",name," ",surname)
setPerson("Gokhan","")
echo("Hello ",name," ",surname)
Hi @gokhan,
It's not safe.
Your Table is storing pointers to the local string variables name and surname. When those local variables go out of scope, your table will contain dangling pointers.
ptr is a non-traced pointer, so it won't keep the strings alive. If you want a traced pointer, use ref instead. But ref can't point to local variables, only to objects that have been allocated on the heap using the built-in new procedure.
Additionally, it seems like each time you call your setPerson proc, it will overwrite the previous values stored by setPerson, because all the values are just being stored in the two local variables name and surname. Is this what you intend?
Thanks. I am aware of that I am writing like "C" in Nim but I am ok with it as long as it works :) What I am trying to achieve is getting maximum performance from Sqlite prepared queries because I am planning to use this for inserting several thousands of rows into database per second. I am trying to minimize unnecessary memory copying. BTW I dont care JS backend. It will not be used in JS anyway and real code I'm dealing with is this:
import tables, streams, sqlite3, db_sqlite
type
TSL3DataType = enum
dtUnknown,
dtInt,
dtFloat,
dtString,
dtBlob
TSL3Data = object
index : int32
bound : bool
case datatype : TSL3DataType
of dtUnknown:
none : pointer
of dtInt:
intVar : ptr int64
of dtFloat:
floatVar : ptr float64
of dtString:
stringVar: ptr string
of dtBlob:
blobVar : pointer
blobSize : cint
PSL3Data = ref TSL3Data
TSL3Statement* = object
db : TDbConn
sl3stmt : Pstmt
columns : Table[string,PSL3Data]
params : Table[string,PSL3Data]
proc check(errCode:cint,msg:string) =
if errCode!=SQLITE_OK:
dbError(msg)
proc prepare*(db: TDbConn, qry: TSqlQuery): TSL3Statement =
var q = qry.cstring
if prepare_v2(db, q, q.len.cint, result.sl3stmt, nil) != SQLITE_OK:
dbError($sqlite3.errmsg(db))
else:
result.db = db
result.columns = initTable[string,PSL3Data]()
result.params = initTable[string,PSL3Data]()
let colCount = column_count(result.sl3stmt)
for i in 0.. colCount-1:
var col : PSL3Data = new TSL3Data
col.index = i
let cName = column_name(result.sl3stmt,i)
var colName = newStringOfCap(len(cName))
add(colName,cName)
result.columns.add(colName,col)
for i in 1.. bind_parameter_count(result.sl3stmt):
var param : PSL3Data = new TSL3Data
param.index = i
var pName = bind_parameter_name(result.sl3stmt,i)
var paramName : string
if pName == nil:
paramName = "#" & $i
else:
paramName = newStringOfCap(len(pName))
add(paramName,pName)
result.params.add(paramName,param)
proc exec*(st: TSL3Statement): int64 =
var sl3Err : cint
for name,param in st.params:
case param.datatype
of dtUnknown:
dbError("Parameter type unknown")
of dtInt:
check(bind_int64(st.sl3stmt,param.index,param.intVar[]),"Parameter binding failed ")
of dtFloat:
check(bind_double(st.sl3stmt,param.index,param.floatVar[]),"Parameter binding failed ")
of dtString:
if param.stringVar[] == nil:
check(bind_null(st.sl3stmt,param.index,),"Parameter binding failed")
else:
check(bind_text(st.sl3stmt,param.index,param.stringVar[].cstring,param.stringVar[].len.cint,0.cint),"Parameter binding failed ")
of dtBlob:
dbError("Blob parameters not implemented yet")
# check(bind_blob(st.sl3stmt,param.index,param.blobVar,param.blobSize,0.cint),"Parameter binding failed ")
sl3Err = step(st.sl3stmt)
if sl3Err == SQLITE_ROW:
for name,col in st.columns:
let colType = column_type(st.sl3stmt,col.index)
case colType
of SQLITE_INTEGER:
if col.datatype != dtInt:
dbError("Integer column expected")
col.intVar[] = column_int64(st.sl3stmt,col.index)
of SQLITE_FLOAT:
if col.datatype != dtFloat:
dbError("Float column expected")
col.floatVar[] = column_double(st.sl3stmt,col.index)
of SQLITE_TEXT:
if col.datatype != dtString:
dbError("String column expected")
let cb = column_bytes(st.sl3stmt, col.index)
if cb == 0:
col.stringVar[] = ""
else:
col.stringVar[] = newStringOfCap(cb)
add(col.stringVar[], column_text(st.sl3stmt, col.index))
of SQLITE_BLOB:
dbError("Blob columns not implemented yet")
else:
dbError("Unknown SQL column type")
result = last_insert_rowid(st.db)
if reset(st.sl3stmt)!=SQLITE_OK:
dbError($sqlite3.errmsg(st.db))
proc bindColumn*(st: var TSL3Statement,name:string,value: var string) =
var col : PSL3Data
col = st.columns.mget(name)
if col.bound:
dbError("Column " & name & " is already bound")
col.datatype = dtString
col.bound = true
if value == nil:
col.stringVar = nil # ? Error
else:
col.stringVar = addr value
proc bindColumn*(st: var TSL3Statement,name:string,value: var int64) =
var col : PSL3Data
col = st.columns.mget(name)
if col.bound:
dbError("Column " & name & " is already bound")
col.datatype = dtInt
col.bound = true
col.intVar = addr value
proc bindColumn*(st: var TSL3Statement,name:string,value: var float64) =
var col : PSL3Data
col = st.columns.mget(name)
if col.bound:
dbError("Column " & name & " is already bound")
col.datatype = dtFloat
col.bound = true
col.floatVar = addr value
proc bindParam*(st: var TSL3Statement,name:string,value: var int64) =
var param = st.params.mget(name)
if param.bound:
dbError("Parameter " & name & " is already bound")
param.datatype = dtInt
param.bound = true
param.intVar = addr value
proc bindParam*(st: var TSL3Statement,name:string,value: var string) =
var param = st.params.mget(name)
if param.bound:
dbError("Parameter " & name & " is already bound")
param.datatype = dtString
param.bound = true
param.stringVar = addr value
proc bindParam(st: var TSL3Statement,name:string,value: var float64) =
var param = st.params.mget(name)
if param.bound:
dbError("Parameter " & name & " is already bound")
param.datatype = dtFloat
param.bound = true
param.floatVar = addr value
when isMainModule:
var db : TDbConn = open("test.db3","", "", "")
var
personID : int64
name : string
surname : string
db.exec(sql"drop table if exists person")
db.exec(sql"create table person(id integer not null primary key,name varchar(25),surname varchar(25))")
var qInsert = db.prepare(sql"insert into person(name,surname) values(:name,:surname)")
qInsert.bindParam(":name",name)
qInsert.bindParam(":surname",surname)
name = "John"
surname = "Doe"
personID = qInsert.exec
name = "Gökhan"
surname = ""
personID = qInsert.exec
var qSelect = db.prepare(sql"select name,surname from person where id=:id")
qSelect.bindColumn("name",name)
qSelect.bindColumn("surname",surname)
qSelect.bindParam(":id",personID)
personID = 1
discard qSelect.exec
echo("Hello ", name," ", surname)
You can definitely use Nim as "better" C. Just be careful if you plan to mix GC and non-GC stuff, there are information about that in tutorials. You can disable the GC completely with compiler flags.
And, the nim stdlib requires GC.