Я пишу приложение, которое должно использовать подготовленные операторы, чтобы получить выигрыш в производительности, но мне интересно, когда у вас есть приложение с десятками, если не сотнями подготовленных операторов, как вы управляете этим, не превращая его в беспорядок глобального кода ? Вам просто нужно подготовить все операторы в конструкторе / функции где-то совершенно иное, чем где они используются?
Использование 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)?
Вы не должны перемещать строку запроса от выполнения запроса.
Если вы пишете вспомогательную функцию, которая подготавливает оператор только тогда, когда это необходимо, вы также избегаете необходимости готовить все из них в начале:
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
звонки выглядят так, как будто вы повторяете их множество раз; создайте для них вспомогательную функцию.)
Других решений пока нет …