Search code examples
c++returnearly-return

How to handle multiple possible early returns from function without lots of duplicate checking code


Given that I do not want to use exceptions in this code, how can I remove the duplicated:

if (rc != SQLITE_OK) {
    return rc;
}

checks after most statements in this function below?

int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
    if (db_ == nullptr) {
        return SQLITE_ERROR;
    }
    
    const std::string sql = update_helper(table_name, fields, where);
    sqlite3_stmt* stmt = NULL;
    int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
    if(rc != SQLITE_OK) {
        return rc;
    }

    // loop thru each parameter, calling bind for each one
    rc = bind_fields(stmt, fields);
    if(rc != SQLITE_OK) {
        return rc;
    }

    // loop thru each where parameter, calling bind for each one
    rc = bind_where(stmt, where);
    if (rc != SQLITE_OK) {
        return rc;
    }

    return step_and_finalise(stmt);
}

Solution

  • First off, you are leaking the sqlite3_stmt if something goes wrong. You need to call sqlite3_finalize() before return'ing anything.

    As for your question, you can wrap the duplicate if..returns in a preprocessor macro, eg:

    #define CHECK_RC(rc) \
    if (rc != SQLITE_OK) \
    { \
        sqlite3_finalize(stmt); \
        return rc; \
    }
    
    int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
        if (db_ == nullptr) {
            return SQLITE_ERROR;
        }
        
        const std::string sql = update_helper(table_name, fields, where);
        sqlite3_stmt* stmt = NULL;
    
        int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
        CHECK_RC(rc);
    
        // loop thru each parameter, calling bind for each one
        rc = bind_fields(stmt, fields);
        CHECK_RC(rc);
    
        // loop thru each where parameter, calling bind for each one
        rc = bind_where(stmt, where);
        CHECK_RC(rc);
    
        return step_and_finalise(stmt);
    }
    

    Alternatively:

    #define CHECK_RC(op) \
    { \
        int rc = op; \
        if (rc != SQLITE_OK) \
        { \
            sqlite3_finalize(stmt); \
            return rc; \
        } \
    }
    
    int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
        if (db_ == nullptr) {
            return SQLITE_ERROR;
        }
        
        const std::string sql = update_helper(table_name, fields, where);
        sqlite3_stmt* stmt = NULL;
    
        CHECK_RC(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
    
        // loop thru each parameter, calling bind for each one
        CHECK_RC(bind_fields(stmt, fields));
    
        // loop thru each where parameter, calling bind for each one
        CHECK_RC(bind_where(stmt, where));
    
        return step_and_finalise(stmt);
    }
    

    Or, you can re-write the code to not use multiple returns, eg:

    int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
        int rc;
        if (db_ == nullptr) {
            rc = SQLITE_ERROR;
        }
        else {    
            const std::string sql = update_helper(table_name, fields, where);
            sqlite3_stmt* stmt = NULL;
    
            rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
            if (rc == SQLITE_OK) {
                // loop thru each parameter, calling bind for each one
                rc = bind_fields(stmt, fields);
                if (rc == SQLITE_OK) {
                    // loop thru each where parameter, calling bind for each one
                    rc = bind_where(stmt, where);
                    if (rc == SQLITE_OK) {
                        rc = step_and_finalise(stmt);
                        stmt = NULL;
                    }
                }
                sqlite3_finalize(stmt);
            }
        }
        return rc;
    }
    

    Or, you can throw an exception, eg:

    void check_rc(int rc)
    {
        if (rc != SQLITE_OK)
            throw rc;
    }
    
    void check_ptr(void* ptr)
    {
        if (ptr == nullptr)
            check_rc(SQLITE_ERROR);
    }
    
    int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
        try {
            check_ptr(db_);
        
            const std::string sql = update_helper(table_name, fields, where);
            sqlite3_stmt* stmt = NULL;
    
            check_rc(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
    
            try {
                // loop thru each parameter, calling bind for each one
                check_rc(bind_fields(stmt, fields));
    
                // loop thru each where parameter, calling bind for each one
                check_rc(bind_where(stmt, where));
    
                return step_and_finalise(stmt);
            }
            catch (const int) {
                sqlite3_finalize(stmt);
                throw;
            }
        }
        catch (const int errCode) {
            return errCode;
        }
    }