Search code examples
sqlitetcl

Ensuring that variable substituion in SQLite from Tcl will always be of the desired type?


What is the proper way to ensure that Tcl variables are treated as the correct type in SQLite?

This active session appears to be about the simplest scenario possible in setting number to 50 but it appears SQLite treats :number as a string, :number+0 and $number as an number.

If, instead, use incr nbr 50 then SQLite treats :nbr as a number.

Is there a way to "declare" the variable in Tcl such that SQLite will treat it as desired; or should every value be cast in the SQL expression?

I'd like to know how to make sure queries will always work as desired rather than just using cast on variables that "fail" to be recognized as desired while testing; and I've yet to recognize some pattern or best practice.

If all variable substitution is not treated as string and cast to a number in the SQL (except when testing reveals an issue), could a future SQLite version result in an instance in which it "just works" changing to the need to cast?

Thank you.

% package require sqlite3
3.43.2
% sqlite3 db
% set number 50
50
% db eval {select min(300, :number)}
300
% db eval {select min(300,:number+0)}
50
% db eval "select min(300,$number)"   
50
% db eval {select min(300, cast(:number as integer))}
50
% incr nbr 50
50
% db eval {select min(300,:nbr)}
50
% db eval "select min(300,$nbr)"
50

% db eval {create table numbers (id integer, value integer);
 insert into numbers values (1,300);}
% db eval {select min(value,:number) from numbers;} 
300

Solution

  • As I recall, if you run the value through expr then it gets a numeric interpretation by SQLite, but you need to be a little careful to stop Tcl from just factoring the value out as a literal when testing:

    set number 50
    set number [expr {$number}]
    db eval {select min(300, :number)}
    

    This tends not to be a big problem when doing more production-oriented code as you put constants in the SQL code and only parameterise values that change (and those will quite possibly have a numeric nature anyway).

    If you're really worried about it, put a CAST in your SQL.


    At the bytecode level, the expr becomes sending the value through an operation called tryCvtToNumeric which gives values a numeric interpretation if they can have one. Literals default to having no interpretation. Formally, we prefer C code to not depend on the existing interpretations of values, but rather to just ask for the one they want, but the tclsqlite extension (called that to distinguish it from the underlying library) doesn't do that and never has.

    It's more important when dealing with BLOB data. That's when you use @var instead of :var...