Mendes
Mendes

Reputation: 18451

C++ Correct way to handle sqlite3_prepare and sqlite3_step errors

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

Answers (1)

Tas
Tas

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

Related Questions