Привязывать 'unsigned long' (uint64) в операторе sqlite3? C ++ - PullRequest
16 голосов
/ 14 декабря 2011

Я использую библиотеку sqlite3, которая доступна по адресу sqlite.org .

У меня есть несколько неподписанных длинных, которые я хотел бы сохранить в базе данных. Я не хочу сам строить запрос и оставлять его открытым для какой-либо инъекции (будь то случайный или нет). Таким образом, я использую функции sqlite_bind_* для «дезинфекции» моих параметров.

Проблема в том, что для длинных целых чисел без знака не существует типа функций, а есть только целые числа.

int sqlite3_bind_int(sqlite3_stmt*, int, int);

int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);

У меня определенно будут номера, которые будут переполнены, если я не смогу хранить их без знака.

Мне нужно будет самому управлять этим? (то есть приведение к типу без знака после выбора из БД или приведение к типу со знаком перед вставкой в ​​базу данных)

Если мне действительно придется управлять этим самостоятельно, как можно было бы выполнить некоторые запросы сравнения, которые хранятся в виде длинного целого числа со знаком, когда сравнение действительно должно быть в диапазоне без знака?

Глядя на конвертируемые типы данных INTEGER , можно подумать, что беззнаковые длинные значения могут быть представлены без проблем.

Если есть другие доступные решения, пожалуйста, просветите меня! Спасибо!

Ответы [ 6 ]

9 голосов
/ 14 декабря 2011

База данных SQLite не имеет возможности хранить в ней 64-разрядные целые числа без знака. Это просто ограничение данных.

Ваши варианты:

  • Сохраните его как строку, преобразовав при необходимости.
  • Сохраните его как двоичный двоичный объект, конвертируя при необходимости.
  • Представьте, что это 64-разрядное целое число со знаком и преобразование при необходимости.
  • Сохранение двух фрагментов информации в виде двух столбцов: 63-разрядное целое число без знака (младшие 63-разрядные) и значение, представляющее знаковый бит.

Поскольку это хэши, вам, вероятно, нет дела до сравнений, кроме проверки на равенство. Таким образом, большинство из этих методов будет хорошо работать для вас.

8 голосов
/ 14 декабря 2011

Если вы хотите сохранить uint64_t в базе данных sqlite и по-прежнему разрешать использовать его как uint64_t в SQL, то вам, вероятно, потребуется написать некоторые пользовательские функции.

Вам просто нужнопреобразовать uint64_t в int64_t при отправке в базу данных и из нее, а затем написать пользовательскую функцию для выполнения любого сравнения и т. д., что вам нужно.Например, чтобы сделать сравнение больше, чем:

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 );

Затем, чтобы использовать это в SQL:

SELECT something FROM aTable WHERE UINT_GT(value1,value2);

В качестве альтернативы, если вам нужно пользовательское сопоставление на основе uint64_t, вы должны иметь возможностьиспользовать sqlite3_create_collation аналогичным образом.

Это не идеальное решение, поскольку вам нужно будет написать пользовательскую функцию для каждой операции, которую вы хотите выполнить, но она должна по крайней мере работать.

1 голос
/ 08 сентября 2015

Я пробовал несколько разных подходов, в основном пытаясь заставить тип столбца работать как sqlite3_uint64, как объяснено в https://www.sqlite.org/c3ref/int64.html

Не повезло, поскольку uint64 не был сохранен в таблице как uint64. Когда я посмотрел на сохраненные данные, они были подписаны, поэтому я предположил, что они были сохранены как int64.

Я закончил префиксом значения одним символом, чтобы sqlite сохранял его как текст без какого-либо преобразования. Гораздо проще было просто добавить символ к значению запроса select для сравнения. Таким образом, 9223360144090060712 стал A9223360144090060712, и я мог бы просто сделать SELECT WHERE HASH = 'A9223360144090060712'

0 голосов
/ 14 декабря 2011

uint32_t полностью помещается внутри int64_t без каких-либо забот, поэтому вы можете выполнить простое задание.

    int64_t i64;
    uint32_t u32 = 32;
    i64 = u32;

Назначая другой способ, которым вы должны проверить границы int64_t, чтобы любые изменения, внесенные в значение в базе данных, были обнаружены раньше.

    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;

Затем вы можете отправить int64 в базу данных sqlite, как обычно.

0 голосов
/ 14 декабря 2011

Лично я обнаружил, что самый простой способ справиться с этой проблемой, в то же время позволяя базе данных выполнять сравнения и т. Д., - это хранить в базе данных целые числа без знака, используя функцию sqlite3_bind_int64.Я предполагаю, что вы используете 32-разрядные целые числа без знака, которые можно легко сохранить в 64-разрядном целом числе со знаком.

Например:

uint32_t x = 0xFFFFFFFF;
sqlite3_bind_int64( stmt, 1, x );

Затем для получения вашей переменной (при условиивы используете boost, в противном случае вам придется написать свой собственный код проверки переполнения):

 #include <boost/numeric/conversion/cast.hpp>
 ...
 uint32_t x = boost::numeric_cast< uint32_t >( sqlite3_column_int64( stmt, 0 ) );

Конечно, это не работает, если вы хотите хранить неподписанные int64, но это немного ограничиваетSQLite.

0 голосов
/ 14 декабря 2011

это хак, но int и unsigned имеют одинаковую битовую глубину, поэтому вы можете

int i=static_cast<int>(unsigned_int);

и преобразовать его обратно

unsigned unsigned_int=static_cast<unsigned>(i);

, чтобы быть в безопасности на вашей платформе

static_assert(sizeof(int)==sizeof(unsigned));

или используйте определенную ширину

#include <stdint.h>
uint32_t
int32_t 
...