Правильное использование функции обратного вызова sqlite3 в С++
У меня есть следующий код С++ для тестирования в сочетании с SQLite3.
Это класс под названием customer
с объявленной функцией обратного вызова. Эта функция обратного вызова вызывается всякий раз, когда sqlite3_exec()
возвращает результаты (записи) из базы данных SQLite.
Что мне не нравится в этой конструкции, так это то, что исходный код для обработки результатов находится в функции обратного вызова вне класса, а не в результатах, обрабатываемых методом класса, из которого вызывается sqlite3_exec()
.
Я мог бы использовать глобальные переменные, которые будут использоваться в методе класса после того, как функция обратного вызова завершит извлечение значений из результатов SQL-запроса. Но что, если есть более одной записи, а функция обратного вызова вызывается несколько раз. Затем мне нужно работать с массивами, если я не буду уверен, что у меня будут только результаты.
Нужно ли забывать о функции обратного вызова и глубже обращаться к SQLite API?
Или мне нужно перейти на С++-оболочку, я полагаю, что там нет механизма обратного вызова, и результаты передаются обратно самому методу класса?
// customer
#include "Customer\customer.h"
//## begin module%50E6CCB50119.additionalDeclarations preserve=yes
static int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
int i;
char* columnName;
char* columnValueString;
short int columnValueShortInt = 0;
int columnValueInt = 0;
cout << "begin of callback function\n";
for(i=0; i<argc; i++)
{
columnName = azColName[i];
if (strcmp(columnName, "FirstName")==0 || strcmp(columnName, "LastName")==0)
{
columnValueString = argv[i];
cout << "columnName = " << columnName << "; value = " << columnValueString <<"\n";
}
else
{
if(strcmp(columnName, "Age")==0)
{
stringstream(argv[i]) >> columnValueShortInt;
cout << "columnName = " << columnName << "; value = " << columnValueShortInt <<"\n";
}
else // strcmp(columnName, "Id")==0)
{
stringstream(argv[i]) >> columnValueInt;
cout << "columnName = " << columnName << "; value = " << columnValueInt <<"\n";
}
}
}
cout << "end of call back function \n";
return 0;
}
//## end module%50E6CCB50119.additionalDeclarations
// Class customer
customer::customer ()
//## begin customer::customer%50F969EE01E4.hasinit preserve=no
//## end customer::customer%50F969EE01E4.hasinit
//## begin customer::customer%50F969EE01E4.initialization preserve=yes
//## end customer::customer%50F969EE01E4.initialization
{
//## begin customer::customer%50F969EE01E4.body preserve=yes
customerId = 0;
zErrMsg = 0;
customerDataBaseRc = sqlite3_open("customerdb", &customerDataBase);
if(customerDataBaseRc)
{
fprintf(stderr, "Can't open database %s\n", sqlite3_errmsg(customerDataBase));
sqlite3_close(customerDataBase);
}
const char * pSQL[6];
const char * sqlStatement;
pSQL[0] = "create table customerTable (Id int, FirstName varchar(30), LastName varchar(30), Age smallint)";
// execute all the sql statements
for(int i = 0; i < 1; i++)
{
customerDataBaseRc = sqlite3_exec(customerDataBase, pSQL[i], callback, 0, &zErrMsg);
if( customerDataBaseRc !=SQLITE_OK )
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
break; // break the loop if error occur
}
}
//## end customer::customer%50F969EE01E4.body
}
customer::~customer ()
{
//## begin customer::~customer%50F93279003E.body preserve=yes
const char *pSQL[6];
// Remove all data in customerTable
pSQL[0] = "delete from customerTable";
// Drop the table from database
pSQL[1] = "drop table customerTable";
// execute all the sql statements
for(int i = 0; i < 2; i++)
{
customerDataBaseRc = sqlite3_exec(customerDataBase, pSQL[i], callback, 0, &zErrMsg);
if( customerDataBaseRc !=SQLITE_OK )
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
break; // break the loop if error occur
}
}
cout << "destructor";
//## end customer::~customer%50F93279003E.body
}
//## Other Operations (implementation)
unsigned int customer::createCustomer (char iCustomerFirstName[20], char iCustomerLastName[20], unsigned short iCustomerAge)
{
//## begin customer::createCustomer%50EBFFA3036B.body preserve=yes
const char *sqlStatement;
string result; // string which will contain the result
ostringstream convert; // stream used for the conversion
convert << "insert into customerTable (Id, FirstName, LastName, Age) values (" << customerId << ", '" << iCustomerFirstName << "', '" << iCustomerLastName << "', " << iCustomerAge << ")";
result = convert.str(); // set 'Result' to the contents of the stream
sqlStatement = result.c_str();
// Execute sql statement
customerDataBaseRc = sqlite3_exec(customerDataBase, sqlStatement, callback, 0, &zErrMsg);
// Check for errors
if(customerDataBaseRc !=SQLITE_OK )
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
return customerId++;
//## end customer::createCustomer%50EBFFA3036B.body
}
char * customer::getCustomer (unsigned int iCustomerId)
{
//## begin customer::getCustomer%50ED3D700186.body preserve=yes
const char *sqlStatement;
char *tmp ="blabla";
string result; // string which will contain the result
ostringstream convert; // stream used for the conversion
convert << "select * from customerTable where Id = " << iCustomerId;
result = convert.str(); // set 'Result' to the contents of the stream
sqlStatement = result.c_str();
// Execute the sql statement
customerDataBaseRc = sqlite3_exec(customerDataBase, sqlStatement, callback, 0, &zErrMsg);
// Check for errors
if(customerDataBaseRc !=SQLITE_OK )
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
return tmp;
//## end customer::getCustomer%50ED3D700186.body
}
// Additional Declarations
//## begin customer%50E6CCB50119.declarations preserve=yes
//## end customer%50E6CCB50119.declarations
//## begin module%50E6CCB50119.epilog preserve=yes
//## end module%50E6CCB50119.epilog
Ответы
Ответ 1
В этом случае обычно используется параметр void *
(который вы называете NotUsed
) обратного вызова - параметр, который вы определяете при установке обратного вызова. Для С++ обычно вы указываете этот параметр указателю this
на ваш заинтересованный объект, и вы должны сделать обратный вызов (функция extern "C"
в исходном файле С++) методом friend
для вашего класса (если необходимо).
Это будет выглядеть так:
class customer
{
...
public:
int callback(int argc, char **argv, char **azColName);
};
static int c_callback(void *param, int argc, char **argv, char **azColName)
{
customer* cust = reinterpret_cast<customer*>(param);
return cust->callback(argc, argv, azColName);
}
char* customer::getCustomer(int id)
{
...
rc = sqlite3_exec(db, sql, c_callback, this, &errMsg);
...
}
int customer::callback(int argc, char **argv, char **azColName)
{
...
}
Ответ 2
Использование sqlite3_exec
имеет недостатки, связанные с тем, что вам нужно преобразовать некоторые значения из строки в число, и что ему необходимо выделить память для всех записей результатов (что может привести к проблемам при чтении больших таблиц).
Кроме того, обратный вызов всегда является отдельной функцией (даже если она находится в том же классе).
Для вашего примера запроса используйте sqlite3_prepare
/sqlite3_step
/ sqlite3_finalize
будет выглядеть следующим образом:
void one_customer::readFromDB(sqlite3* db, int id)
{
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, "SELECT FirstName, LastName, Age"
" FROM customerTable"
" WHERE Id = ?", -1, &stmt, NULL);
if (rc != SQLITE_OK)
throw string(sqlite3_errmsg(db));
rc = sqlite3_bind_int(stmt, 1, id); // Using parameters ("?") is not
if (rc != SQLITE_OK) { // really necessary, but recommended
string errmsg(sqlite3_errmsg(db)); // (especially for strings) to avoid
sqlite3_finalize(stmt); // formatting problems and SQL
throw errmsg; // injection attacks.
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
string errmsg(sqlite3_errmsg(db));
sqlite3_finalize(stmt);
throw errmsg;
}
if (rc == SQLITE_DONE) {
sqlite3_finalize(stmt);
throw string("customer not found");
}
this->id = id;
this->first_name = string(sqlite3_column_text(stmt, 0));
this->last_name = string(sqlite3_column_text(stmt, 1));
this->age = sqlite3_column_int(stmt, 2);
sqlite3_finalize(stmt);
}
(Этот код обрабатывает ошибки, просто выбросив string
с сообщением об ошибке.)