Reputation: 18451
I have a function I´m building using C++ and Sqlite3 where I will be issuing a simple SQL statement.
This function expects DML commands with no return (like: INSERT INTO, UPDATE or CREATE type statements).
I wish to know if I´m handling correct the possible errors on sqlite3 calls. This is the code I´m using:
void execStatement(sqlite* dbHandler, std::string sql)
{
sqlite3_stmt* compiledStatement;
int retStatus = sqlite3_prepare(dbHandler, sql.c_str(), -1, &compiledStatement, 0);
if (retStatus != SQLITE_OK)
{
sqlite3_finalize(compiledStatement); // <-- Correct ?
std::string errorMessage(sqlite3_errmsg(dbHandler));
throw MyException(retStatus, errorMessage); // MyException accepts 2 params: int and std::string
}
retStatus = sqlite3_step(compiledStatement);
if (retStatus == SQLITE_ERROR || retStatus == SQLITE_BUSY) // <- Correct ?
{
sqlite3_finalize(compiledStatement); <-- Correct ?
std::string errorMessage(sqlite3_errmsg(dbHandler));
throw MyException(retStatus, errorMessage);
}
return;
}
So, basically issuing finalize would be enough to keep things on track without affecting later calls to same database ?
Thanks for helping.
Upvotes: 4
Views: 9252
Reputation: 7111
Whilst I can't help with the use of sqlite3_prepare
, sqlite3_step
and sqlite_finalize
, out of interest, why don't you just use sqlite3_exec
(which wraps all those functions up): https://www.sqlite.org/c3ref/exec.html?
sqlite_exec
can be used like the following:
sqlite3* pDB;
if (sqlite3_open("Database", &pDB) != SQLITE_OK)
{
// handle it
}
// Execute a command:
char* error = nullptr;
if (sqlite3_exec(pDB, "SELECT * FROM SomeTable WHERE Something='Hi'", _func, &object, &error) != SQLITE_OK)
{
// Ruh-roh, there was an error:
cout << error << endl;
sqlite3_free(error);
}
sqlite3_exec
will execute _func
and pass object
to each row it finds that matches the condition.
As a more concrete example, suppose we had the following table
create_table Customers(id int, name string, surname string, postcode string)
We wish to find all customers within a postcode:
int getCustomers(void* pObject, int columns, char** columnValues, char** columnNames)
{
if (columns != columns_in_desired_table)
{
// we expect this to be called on a specific table. if it's not called on a specific
// table then the result is rubbish
return 1;
}
// ensure we received the right object:
if (std::vector<std::string>* name = reinterpret_cast<std::vector<std::string>*>(pObject))
name->push_back(columnValues[1]);
// indicate success with zero:
return 0;
}
int main()
{
sqlite3* db;
sqlite3_open(&db, "customers.db");
// this will be assigned from our callback function
std::vector<std::string> customers;
char* error = nullptr;
// getCustomerswill be called on EACH row in Customers
sqlite3_exec(db, "select * from Customers where postcode='1234'", &getCustomers, static_cast<void*>(&customers), error);
}
I've omitted any error handling here
Here, getCustomers
is our callback function, and its signature matches what is expected
int (*callback)(void*,int,char**,char**)
getCustomers
will be called on each row in the table Customers that matches the SQL query:
select * from Customers where postcode='1234'
customers
will get populated with all entries from our table. If no entries are found, the callback is not called and the vector
will remain empty. If there is an error, error
will be populated with the error message and a call to sqlite3_free
will be needed to free the memory (we pass in nullptr
, so sqlite3 handles the memory for it).
Upvotes: 3