Организация sqlite3 C / C ++ подготовленных операторов (избегая путаницы в глобальном коде)

Я пишу приложение, которое должно использовать подготовленные операторы, чтобы получить выигрыш в производительности, но мне интересно, когда у вас есть приложение с десятками, если не сотнями подготовленных операторов, как вы управляете этим, не превращая его в беспорядок глобального кода ? Вам просто нужно подготовить все операторы в конструкторе / функции где-то совершенно иное, чем где они используются?

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

В частности, сейчас у меня просто синглтон базы данных, который имеет частные переменные,

sqlite3_stmt *insertPacketCount;
sqlite3_stmt *insertPortContacted;
sqlite3_stmt *insertPacketSize;
sqlite3_stmt *computePacketSizeVariance;
...

С конструктором, имеющим десятки вызовов sqlite3_prepare_v2,

// Set up all our prepared queries
res = sqlite3_prepare_v2(db,
"INSERT OR IGNORE INTO packet_counts VALUES(?1, ?2, ?3, 1);",
-1, &insertPacketCount,  NULL);
expectReturnValueAndFail(SQLITE_OK);
...

И фактические функции, которые используют их в другом месте,

void Database::IncrementPacketCount(std::string ip, std::string interface, std::string type, uint64_t increment)
{
int res;

res = sqlite3_bind_text(incrementPacketCount, 1, ip.c_str(), -1, SQLITE_STATIC);
expectReturnValue(SQLITE_OK);
res = sqlite3_bind_text(incrementPacketCount, 2, interface.c_str(), -1, SQLITE_STATIC);
expectReturnValue(SQLITE_OK);
res = sqlite3_bind_text(incrementPacketCount, 3, type.c_str(), -1, SQLITE_STATIC);
expectReturnValue(SQLITE_OK);
res = sqlite3_bind_int(incrementPacketCount, 4, increment);
expectReturnValue(SQLITE_OK);

res = sqlite3_step(incrementPacketCount);
expectReturnValue(SQLITE_DONE);
res = sqlite3_reset(incrementPacketCount);
expectReturnValue(SQLITE_OK);
}

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

Извините, если это расплывчато, но есть ли какие-либо библиотеки или методы для организации такого рода вещей без ущерба для производительности / безопасности (например, просто добавление строк и вызов sqlite_exec)?

3

Решение

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

void Database::PrepareStatementIfNeeded(sqlite3_stmt **stmt,
const std::string& sql)
{
if (*stmt == NULL) {
res = sqlite3_prepare_v2(db, sql.c_str(), -1, stmt, NULL);
...
}
}

(Но вы все равно должны убедиться, что все они доработаны.)

Кроме того, если вы дадите имена параметров вместо чисел, вы можете использовать sqlite3_bind_parameter_index чтобы получить правильный индекс:

void BindParam(sqlite3_stmt *stmt,
const char *name,
const std::string& value) // overload for other types
{
int index = sqlite3_bind_parameter_index(stmt, name);
if (index == 0)
error...;
int res = sqlite3_bind_text(stmt, index, value.c_str(), -1, SQLITE_TRANSIENT);
...
}

void Database::IncrementPacketCount(...)
{
PrepareStatementIfNeeded(&incrementPacketCount,
"INSERT OR IGNORE INTO packet_counts"" VALUES(:ip, :intf, :type, 1)");
BindParameter(incrementPacketCount, "ip",   ip);
BindParameter(incrementPacketCount, "intf", interface);
BindParameter(incrementPacketCount, "type", type);
...
}

(И те sqlite3_step/sqlite3_reset звонки выглядят так, как будто вы повторяете их множество раз; создайте для них вспомогательную функцию.)

2

Другие решения

Других решений пока нет …