Reputation: 10941
I'm using the sqlite3 library that is available at sqlite.org.
I have some unsigned longs that I would like store in a database. I do not want to construct the query myself and leave it open to some sort of injection (whether it be accidental or not). Thus, I'm using the sqlite_bind_*
functions to 'sanitize' my parameters.
The issue is that there isn't a function type for unsigned long integers, just integers.
int sqlite3_bind_int(sqlite3_stmt*, int, int);
int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
I am definitely going to have numbers that will overflow if I am unable to store them in an unsigned manner.
Am I going to need to manage this myself? (i.e. casting to an unsigned type after selecting from the db or casting to signed type before inserting into database)
If I do have to manage this myself, how would one do some comparison queries that are stored as a signed long integer when the comparisons are really meant to be in the unsigned range?
Looking at the INTEGER datatypes that get converted, one would think that unsigned longs could be represented without issue.
If there are other solutions available, please enlighten me! Thanks!
Upvotes: 19
Views: 29504
Reputation: 473577
An SQLite database does not have the ability to store unsigned 64-bit integers in it. It's just a limitation of the data.
Your options are:
Since these are hashes, you probably don't care about comparisons other than equality testing. So most of these methods would work just fine for you.
Upvotes: 16
Reputation: 659
I tried a number of different approaches, mainly trying to get the column type to work as a sqlite3_uint64 as explained at https://www.sqlite.org/c3ref/int64.html
Had no luck as the uint64 was not stored as a uint64 in the table. When I looked at the stored data it was signed, so I assumed it was stored as int64.
I ended up prefixing the value with a single char to force sqlite to store it as text without any conversion taking place. It was much easier to just add the char to the select query value for comparison. Thus 9223360144090060712 became A9223360144090060712, and I could simply do a SELECT WHERE HASH = 'A9223360144090060712'
Upvotes: 1
Reputation: 9569
If you wanted to store a uint64_t in a sqlite database and still allow it to be used as a uint64_t in SQL, then you're probably going to need to write some custom functions.
You simply need to cast the uint64_t to an int64_t when sending to and from the database, and then write a custom function to perform whatever comparison etc. you need. For example, to do a greater than comparison:
void greaterThan( sqlite3_context* ctx, sqlite3_value** values )
{
uint64_t value1 = boost::numeric_cast< uint64_t >( sqlite3_value_int64( value[ 0 ] ) );
uint64_t value2 = boost::numeric_cast< uint64_t >( sqlite3_value_int64( value[ 1 ] ) );
sqlite3_result_int( ctx, value1 > value2 );
}
//Register this function using sqlite3_create_function
sqlite3_create_function( db, "UINT_GT", 2, SQLITE3_ANY, NULL, &greaterThan, NULL, NULL );
Then to use this in SQL:
SELECT something FROM aTable WHERE UINT_GT(value1,value2);
Alternatively, if you need custom collation based on uint64_t, you should be able to use sqlite3_create_collation in a similar manner.
It's not a perfect solution as you'll need to write a custom function for every operation you want to do, but it should at least work.
Upvotes: 13
Reputation: 9569
Personally, I've found that the easiest way to deal with this problem while still allowing the database to perform comparisons etc. is to store unsigned integers in the database using the sqlite3_bind_int64
function. I'm assuming that you are using 32 bit unsigned integers, which can easily be stored in a 64 bit signed integer.
For example:
uint32_t x = 0xFFFFFFFF;
sqlite3_bind_int64( stmt, 1, x );
Then to retrieve your variable (assuming you're using boost, otherwise you'll have to write your own overflow check code):
#include <boost/numeric/conversion/cast.hpp>
...
uint32_t x = boost::numeric_cast< uint32_t >( sqlite3_column_int64( stmt, 0 ) );
Of course this falls apart if you want to store unsigned int64s, but that's a bit of a limitation of sqlite.
Upvotes: 0
Reputation: 10720
uint32_t
fits entirely inside of a int64_t
without any worries, so you can do a simple assignment.
int64_t i64;
uint32_t u32 = 32;
i64 = u32;
Assigning the other way you should check the bounds of the int64_t
so that any changes made to the value inside of the database are caught early.
int64_t i64 = 32;
uint32_t u32;
if (i64 < 0 || i64 > std::numeric_limits<uint32_t>::max())
throw std::runtime_error("invalid uint32_t");
u32 = i64;
You can then send your int64
to the sqlite database as you normally would.
Upvotes: 1
Reputation: 16158
it is a hack but int and unsigned have the same bit depth so you could
int i=static_cast<int>(unsigned_int);
and convert it back
unsigned unsigned_int=static_cast<unsigned>(i);
to be on the safe side on your plaftform
static_assert(sizeof(int)==sizeof(unsigned));
or use the width specific
#include <stdint.h>
uint32_t
int32_t
Upvotes: 0