diff --git a/.gitignore b/.gitignore index f22bfa25..e6ea6912 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build example1 *.a +.vs/ .vscode/ /SQLiteCpp.sln *.ncb diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb50a05..2c4e244f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ set(SQLITECPP_SRC ${PROJECT_SOURCE_DIR}/src/Column.cpp ${PROJECT_SOURCE_DIR}/src/Database.cpp ${PROJECT_SOURCE_DIR}/src/Exception.cpp + ${PROJECT_SOURCE_DIR}/src/RowExecutor.cpp ${PROJECT_SOURCE_DIR}/src/Savepoint.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp ${PROJECT_SOURCE_DIR}/src/Transaction.cpp @@ -119,6 +120,7 @@ set(SQLITECPP_INC ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Column.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Database.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Exception.h + ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/RowExecutor.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Savepoint.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index bc349f98..77cab679 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -283,7 +283,7 @@ T Statement::getColumns() template T Statement::getColumns(const std::integer_sequence) { - return T{Column(mpPreparedStatement, Is)...}; + return T{Column(getStatement(), Is)...}; } #endif diff --git a/include/SQLiteCpp/RowExecutor.h b/include/SQLiteCpp/RowExecutor.h new file mode 100644 index 00000000..d3ca632d --- /dev/null +++ b/include/SQLiteCpp/RowExecutor.h @@ -0,0 +1,234 @@ +/** + * @file RowExecutor.h + * @ingroup SQLiteCpp + * @brief TODO: + * + * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + + //#include + #include + +#include +#include +#include + +// Forward declaration to avoid inclusion of in a header +struct sqlite3_stmt; + +namespace SQLite +{ + +extern const int OK; ///< SQLITE_OK + + +class RowExecutor +{ +public: + /// Shared pointer to SQLite Prepared Statement Object + using TStatementPtr = std::shared_ptr; + + /// Weak pointer to SQLite Prepared Statement Object + using TStatementWeakPtr = std::weak_ptr; + + /// Shared pointer to SQLite RowExecutor + using TRowPtr = std::shared_ptr; + + /// Weak pointer to SQLite RowExecutor + using TRowWeakPtr = std::weak_ptr; + + /// Type to store columns names and indexes + using TColumnsMap = std::map>; + + /// Reset the statement to make it ready for a new execution. Throws an exception on error. + void reset(); + + /// Reset the statement. Returns the sqlite result code instead of throwing an exception on error. + int tryReset() noexcept; + + /** + * @brief Execute a step of the prepared query to fetch one row of results. + * + * While true is returned, a row of results is available, and can be accessed + * through the getColumn() method + * + * @see exec() execute a one-step prepared statement with no expected result + * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. + * @see Database::exec() is a shortcut to execute one or multiple statements without results + * + * @return - true (SQLITE_ROW) if there is another row ready : you can call getColumn(N) to get it + * then you have to call executeStep() again to fetch more rows until the query is finished + * - false (SQLITE_DONE) if the query has finished executing : there is no (more) row of result + * (case of a query with no result, or after N rows fetched successfully) + * + * @throw SQLite::Exception in case of error + */ + bool executeStep(); + + /** + * @brief Try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. + * + * + * + * @see exec() execute a one-step prepared statement with no expected result + * @see executeStep() execute a step of the prepared query to fetch one row of results + * @see Database::exec() is a shortcut to execute one or multiple statements without results + * + * @return the sqlite result code. + */ + int tryExecuteStep() noexcept; + + /** + * @brief Execute a one-step query with no expected result, and return the number of changes. + * + * This method is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : + * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" + * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" + * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" + * + * It is similar to Database::exec(), but using a precompiled statement, it adds : + * - the ability to bind() arguments to it (best way to insert data), + * - reusing it allows for better performances (efficient for multiple insertion). + * + * @see executeStep() execute a step of the prepared query to fetch one row of results + * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. + * @see Database::exec() is a shortcut to execute one or multiple statements without results + * + * @return number of row modified by this SQL statement (INSERT, UPDATE or DELETE) + * + * @throw SQLite::Exception in case of error, or if row of results are returned while they are not expected! + */ + int exec(); + + //////////////////////////////////////////////////////////////////////////// + + /** + * @brief Execute a step of the prepared query to fetch one row of results. + * + * While true is returned, a row of results is available, and can be accessed + * through the getColumn() method + * + * @see exec() execute a one-step prepared statement with no expected result + * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. + * @see Database::exec() is a shortcut to execute one or multiple statements without results + * + * @return - true (SQLITE_ROW) if there is another row ready : you can call getColumn(N) to get it + * then you have to call executeStep() again to fetch more rows until the query is finished + * - false (SQLITE_DONE) if the query has finished executing : there is no (more) row of result + * (case of a query with no result, or after N rows fetched successfully) + * + * @throw SQLite::Exception in case of error + */ + const TColumnsMap& getColumnsNames() const; + + /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). + int getChanges() const noexcept; + + /// Return the number of columns in the result set returned by the prepared statement + int getColumnCount() const + { + return mColumnCount; + } + /// true when a row has been fetched with executeStep() + bool hasRow() const + { + return mbHasRow; + } + /// true when the last executeStep() had no more row to fetch + bool isDone() const + { + return mbDone; + } + + //////////////////////////////////////////////////////////////////////////// + + /// Return the numeric result code for the most recent failed API call (if any). + int getErrorCode() const noexcept; + + /// Return the extended numeric result code for the most recent failed API call (if any). + int getExtendedErrorCode() const noexcept; + + /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). + const char* getErrorMsg() const noexcept; + +protected: + /** + * + */ + explicit RowExecutor(sqlite3* apSQLite, const std::string& aQuery); + + /** + * @brief Return a std::shared_ptr with SQLite Statement Object. + * + * @return raw pointer to Statement Object + */ + TStatementPtr getStatement() const noexcept; + + /** + * @brief Return a prepared SQLite Statement Object. + * + * Throw an exception if the statement object was not prepared. + * @return raw pointer to Prepared Statement Object + */ + sqlite3_stmt* getPreparedStatement() const; + + /** + * @brief Check if a return code equals SQLITE_OK, else throw a SQLite::Exception with the SQLite error message + * + * @param[in] aRet SQLite return code to test against the SQLITE_OK expected value + */ + void check(const int aRet) const + { + if (SQLite::OK != aRet) + { + throw SQLite::Exception(mpSQLite, aRet); + } + } + + /** + * @brief Check if there is a row of result returned by executeStep(), else throw a SQLite::Exception. + */ + void checkRow() const + { + if (false == mbHasRow) + { + throw SQLite::Exception("No row to get a column from. executeStep() was not called, or returned false."); + } + } + + /** + * @brief Check if there is a Column index is in the range of columns in the result. + */ + void checkIndex(const int aIndex) const + { + if ((aIndex < 0) || (aIndex >= mColumnCount)) + { + throw SQLite::Exception("Column index out of range."); + } + } + +private: + /// Create prepared SQLite Statement Object + void prepareStatement(const std::string& aQuery); + + /// Get column number and create map with columns names + void createColumnInfo(); + + sqlite3* mpSQLite{}; //!< Pointer to SQLite Database Connection Handle + TStatementPtr mpStatement{}; //!< Shared Pointer to the prepared SQLite Statement Object + + int mColumnCount{ 0 }; //!< Number of columns in the result of the prepared statement + bool mbHasRow{ false }; //!< true when a row has been fetched with executeStep() + bool mbDone{ false }; //!< true when the last executeStep() had no more row to fetch + + /// Map of columns index by name (mutable so getColumnIndex can be const) + mutable TColumnsMap mColumnNames{}; +}; + + +} // namespace SQLite diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index c81e215d..cd145e16 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -10,16 +10,15 @@ */ #pragma once +#include +#include #include #include // SQLITECPP_PURE_FUNC #include #include #include - -// Forward declarations to avoid inclusion of in a header -struct sqlite3; -struct sqlite3_stmt; +#include namespace SQLite @@ -30,7 +29,6 @@ namespace SQLite class Database; class Column; -extern const int OK; ///< SQLITE_OK /** * @brief RAII encapsulation of a prepared SQLite Statement. @@ -49,19 +47,9 @@ extern const int OK; ///< SQLITE_OK * because of the way it shares the underling SQLite precompiled statement * in a custom shared pointer (See the inner class "Statement::Ptr"). */ -class Statement +class Statement : public RowExecutor { public: - /** - * @brief Compile and register the SQL query for the provided SQLite Database Connection - * - * @param[in] aDatabase the SQLite Database Connection - * @param[in] apQuery an UTF-8 encoded query string - * - * Exception is thrown in case of error, then the Statement object is NOT constructed. - */ - Statement(const Database& aDatabase, const char* apQuery); - /** * @brief Compile and register the SQL query for the provided SQLite Database Connection * @@ -70,16 +58,14 @@ class Statement * * Exception is thrown in case of error, then the Statement object is NOT constructed. */ - Statement(const Database& aDatabase, const std::string& aQuery) : - Statement(aDatabase, aQuery.c_str()) - {} + Statement(const Database& aDatabase, const std::string& aQuery); /** * @brief Move an SQLite statement. * * @param[in] aStatement Statement to move */ - Statement(Statement&& aStatement) noexcept; + Statement(Statement&& aStatement) noexcept = default; Statement& operator=(Statement&& aStatement) noexcept = default; // Statement is non-copyable @@ -90,12 +76,6 @@ class Statement /// The finalization will be done by the destructor of the last shared pointer ~Statement() = default; - /// Reset the statement to make it ready for a new execution. Throws an exception on error. - void reset(); - - /// Reset the statement. Returns the sqlite result code instead of throwing an exception on error. - int tryReset() noexcept; - /** * @brief Clears away all the bindings of a prepared statement. * @@ -382,62 +362,6 @@ class Statement //////////////////////////////////////////////////////////////////////////// - /** - * @brief Execute a step of the prepared query to fetch one row of results. - * - * While true is returned, a row of results is available, and can be accessed - * through the getColumn() method - * - * @see exec() execute a one-step prepared statement with no expected result - * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return - true (SQLITE_ROW) if there is another row ready : you can call getColumn(N) to get it - * then you have to call executeStep() again to fetch more rows until the query is finished - * - false (SQLITE_DONE) if the query has finished executing : there is no (more) row of result - * (case of a query with no result, or after N rows fetched successfully) - * - * @throw SQLite::Exception in case of error - */ - bool executeStep(); - - /** - * @brief Try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * - * - * - * @see exec() execute a one-step prepared statement with no expected result - * @see executeStep() execute a step of the prepared query to fetch one row of results - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return the sqlite result code. - */ - int tryExecuteStep() noexcept; - - /** - * @brief Execute a one-step query with no expected result, and return the number of changes. - * - * This method is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * It is similar to Database::exec(), but using a precompiled statement, it adds : - * - the ability to bind() arguments to it (best way to insert data), - * - reusing it allows for better performances (efficient for multiple insertion). - * - * @see executeStep() execute a step of the prepared query to fetch one row of results - * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return number of row modified by this SQL statement (INSERT, UPDATE or DELETE) - * - * @throw SQLite::Exception in case of error, or if row of results are returned while they are not expected! - */ - int exec(); - - //////////////////////////////////////////////////////////////////////////// - /** * @brief Return a copy of the column data specified by its index * @@ -603,11 +527,6 @@ class Statement */ const char * getColumnDeclaredType(const int aIndex) const; - - /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). - int getChanges() const noexcept; - - //////////////////////////////////////////////////////////////////////////// /// Return the UTF-8 SQL Query. @@ -619,95 +538,11 @@ class Statement // Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. std::string getExpandedSQL() const; - /// Return the number of columns in the result set returned by the prepared statement - int getColumnCount() const - { - return mColumnCount; - } - /// true when a row has been fetched with executeStep() - bool hasRow() const - { - return mbHasRow; - } - /// true when the last executeStep() had no more row to fetch - bool isDone() const - { - return mbDone; - } - /// Return the number of bind parameters in the statement int getBindParameterCount() const noexcept; - /// Return the numeric result code for the most recent failed API call (if any). - int getErrorCode() const noexcept; - /// Return the extended numeric result code for the most recent failed API call (if any). - int getExtendedErrorCode() const noexcept; - /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). - const char* getErrorMsg() const noexcept; - - /// Shared pointer to SQLite Prepared Statement Object - using TStatementPtr = std::shared_ptr; - private: - /** - * @brief Check if a return code equals SQLITE_OK, else throw a SQLite::Exception with the SQLite error message - * - * @param[in] aRet SQLite return code to test against the SQLITE_OK expected value - */ - void check(const int aRet) const - { - if (SQLite::OK != aRet) - { - throw SQLite::Exception(mpSQLite, aRet); - } - } - - /** - * @brief Check if there is a row of result returned by executeStep(), else throw a SQLite::Exception. - */ - void checkRow() const - { - if (false == mbHasRow) - { - throw SQLite::Exception("No row to get a column from. executeStep() was not called, or returned false."); - } - } - - /** - * @brief Check if there is a Column index is in the range of columns in the result. - */ - void checkIndex(const int aIndex) const - { - if ((aIndex < 0) || (aIndex >= mColumnCount)) - { - throw SQLite::Exception("Column index out of range."); - } - } - - /** - * @brief Prepare statement object. - * - * @return Shared pointer to prepared statement object - */ - TStatementPtr prepareStatement(); - - /** - * @brief Return a prepared statement object. - * - * Throw an exception if the statement object was not prepared. - * @return raw pointer to Prepared Statement Object - */ - sqlite3_stmt* getPreparedStatement() const; - std::string mQuery; //!< UTF-8 SQL Query - sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle - TStatementPtr mpPreparedStatement; //!< Shared Pointer to the prepared SQLite Statement Object - int mColumnCount{0}; //!< Number of columns in the result of the prepared statement - bool mbHasRow{false}; //!< true when a row has been fetched with executeStep() - bool mbDone{false}; //!< true when the last executeStep() had no more row to fetch - - /// Map of columns index by name (mutable so getColumnIndex can be const) - mutable std::map mColumnNames{}; }; diff --git a/src/RowExecutor.cpp b/src/RowExecutor.cpp new file mode 100644 index 00000000..1c0b757e --- /dev/null +++ b/src/RowExecutor.cpp @@ -0,0 +1,186 @@ +/** + * @file RowExecutor.cpp + * @ingroup SQLiteCpp + * @brief TODO: + * + * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ +#include + +#include + +#include + +namespace SQLite +{ + + + RowExecutor::RowExecutor(sqlite3* apSQLite, const std::string& aQuery) + : mpSQLite(apSQLite) + { + prepareStatement(aQuery); + createColumnInfo(); + } + + void SQLite::RowExecutor::prepareStatement(const std::string& aQuery) + { + if (!mpSQLite) + throw SQLite::Exception("Can't create statement without valid database connection"); + + sqlite3_stmt* statement; + const int ret = sqlite3_prepare_v2(mpSQLite, aQuery.c_str(), static_cast(aQuery.size()), &statement, nullptr); + if (SQLITE_OK != ret) + { + throw SQLite::Exception(mpSQLite, ret); + } + mpStatement = TStatementPtr(statement, [](sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + }); + } + + void SQLite::RowExecutor::createColumnInfo() + { + mColumnCount = sqlite3_column_count(mpStatement.get()); + + + if (!mColumnNames.empty()) + mColumnNames.clear(); + + // Build the map of column name and index + for (int i = 0; i < mColumnCount; ++i) + { + const char* pName = sqlite3_column_name(getPreparedStatement(), i); + mColumnNames.emplace(pName, i); + } + } + + // Reset the statement to make it ready for a new execution (see also #clearBindings() bellow) + void RowExecutor::reset() + { + const int ret = tryReset(); + check(ret); + } + + int RowExecutor::tryReset() noexcept + { + mbHasRow = false; + mbDone = false; + return sqlite3_reset(mpStatement.get()); + } + + // Execute a step of the query to fetch one row of results + bool RowExecutor::executeStep() + { + const int ret = tryExecuteStep(); + if ((SQLITE_ROW != ret) && (SQLITE_DONE != ret)) // on row or no (more) row ready, else it's a problem + { + if (ret == sqlite3_errcode(mpSQLite)) + { + throw SQLite::Exception(mpSQLite, ret); + } + else + { + throw SQLite::Exception("Statement needs to be reseted", ret); + } + } + + return mbHasRow; // true only if one row is accessible by getColumn(N) + } + + // Execute a one-step query with no expected result, and return the number of changes. + int RowExecutor::exec() + { + const int ret = tryExecuteStep(); + if (SQLITE_DONE != ret) // the statement has finished executing successfully + { + if (SQLITE_ROW == ret) + { + throw SQLite::Exception("exec() does not expect results. Use executeStep."); + } + else if (ret == sqlite3_errcode(mpSQLite)) + { + throw SQLite::Exception(mpSQLite, ret); + } + else + { + throw SQLite::Exception("Statement needs to be reseted", ret); + } + } + + // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE) + return sqlite3_changes(mpSQLite); + } + + int RowExecutor::tryExecuteStep() noexcept + { + if (mbDone) + { + return SQLITE_MISUSE; // Statement needs to be reseted ! + } + + const int ret = sqlite3_step(mpStatement.get()); + if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it + { + mbHasRow = true; + } + else + { + mbHasRow = false; + mbDone = SQLITE_DONE == ret; // check if the query has finished executing + } + return ret; + } + + const RowExecutor::TColumnsMap& RowExecutor::getColumnsNames() const + { + return mColumnNames; + } + + // Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). + int RowExecutor::getChanges() const noexcept + { + return sqlite3_changes(mpSQLite); + } + + // Return the numeric result code for the most recent failed API call (if any). + int RowExecutor::getErrorCode() const noexcept + { + return sqlite3_errcode(mpSQLite); + } + + // Return the extended numeric result code for the most recent failed API call (if any). + int RowExecutor::getExtendedErrorCode() const noexcept + { + return sqlite3_extended_errcode(mpSQLite); + } + + // Return UTF-8 encoded English language explanation of the most recent failed API call (if any). + const char* RowExecutor::getErrorMsg() const noexcept + { + return sqlite3_errmsg(mpSQLite); + } + + // Return std::shared_ptr with SQLite statement object + RowExecutor::TStatementPtr RowExecutor::getStatement() const noexcept + { + return mpStatement; + } + + // Return prepered SQLite statement object or throw + sqlite3_stmt* RowExecutor::getPreparedStatement() const + { + sqlite3_stmt* ret = mpStatement.get(); + if (ret) + { + return ret; + } + throw SQLite::Exception("Statement was not prepared."); + } + + +} // namespace SQLite diff --git a/src/Statement.cpp b/src/Statement.cpp index 64e16d2d..5ee8cf88 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -20,42 +20,11 @@ namespace SQLite { -Statement::Statement(const Database& aDatabase, const char* apQuery) : - mQuery(apQuery), - mpSQLite(aDatabase.getHandle()), - mpPreparedStatement(prepareStatement()) // prepare the SQL query (needs Database friendship) -{ - mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); -} - -Statement::Statement(Statement&& aStatement) noexcept : - mQuery(std::move(aStatement.mQuery)), - mpSQLite(aStatement.mpSQLite), - mpPreparedStatement(std::move(aStatement.mpPreparedStatement)), - mColumnCount(aStatement.mColumnCount), - mbHasRow(aStatement.mbHasRow), - mbDone(aStatement.mbDone), - mColumnNames(std::move(aStatement.mColumnNames)) -{ - aStatement.mpSQLite = nullptr; - aStatement.mColumnCount = 0; - aStatement.mbHasRow = false; - aStatement.mbDone = false; -} -// Reset the statement to make it ready for a new execution (see also #clearBindings() bellow) -void Statement::reset() -{ - const int ret = tryReset(); - check(ret); -} - -int Statement::tryReset() noexcept -{ - mbHasRow = false; - mbDone = false; - return sqlite3_reset(mpPreparedStatement.get()); -} +Statement::Statement(const Database& aDatabase, const std::string& aQuery) : + RowExecutor(aDatabase.getHandle(), aQuery), + mQuery(aQuery) +{} // Clears away all the bindings of a prepared statement (can be associated with #reset() above). void Statement::clearBindings() @@ -64,6 +33,7 @@ void Statement::clearBindings() check(ret); } +// Get bind parameter index int Statement::getIndex(const char * const apName) const { return sqlite3_bind_parameter_index(getPreparedStatement(), apName); @@ -148,71 +118,6 @@ void Statement::bind(const int aIndex) check(ret); } - -// Execute a step of the query to fetch one row of results -bool Statement::executeStep() -{ - const int ret = tryExecuteStep(); - if ((SQLITE_ROW != ret) && (SQLITE_DONE != ret)) // on row or no (more) row ready, else it's a problem - { - if (ret == sqlite3_errcode(mpSQLite)) - { - throw SQLite::Exception(mpSQLite, ret); - } - else - { - throw SQLite::Exception("Statement needs to be reseted", ret); - } - } - - return mbHasRow; // true only if one row is accessible by getColumn(N) -} - -// Execute a one-step query with no expected result, and return the number of changes. -int Statement::exec() -{ - const int ret = tryExecuteStep(); - if (SQLITE_DONE != ret) // the statement has finished executing successfully - { - if (SQLITE_ROW == ret) - { - throw SQLite::Exception("exec() does not expect results. Use executeStep."); - } - else if (ret == sqlite3_errcode(mpSQLite)) - { - throw SQLite::Exception(mpSQLite, ret); - } - else - { - throw SQLite::Exception("Statement needs to be reseted", ret); - } - } - - // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE) - return sqlite3_changes(mpSQLite); -} - -int Statement::tryExecuteStep() noexcept -{ - if (mbDone) - { - return SQLITE_MISUSE; // Statement needs to be reseted ! - } - - const int ret = sqlite3_step(mpPreparedStatement.get()); - if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it - { - mbHasRow = true; - } - else - { - mbHasRow = false; - mbDone = SQLITE_DONE == ret; // check if the query has finished executing - } - return ret; -} - - // Return a copy of the column data specified by its index starting at 0 // (use the Column copy-constructor) Column Statement::getColumn(const int aIndex) const @@ -221,7 +126,7 @@ Column Statement::getColumn(const int aIndex) const checkIndex(aIndex); // Share the Statement Object handle with the new Column created - return Column(mpPreparedStatement, aIndex); + return Column(getStatement(), aIndex); } // Return a copy of the column data specified by its column name starting at 0 @@ -232,7 +137,7 @@ Column Statement::getColumn(const char* apName) const const int index = getColumnIndex(apName); // Share the Statement Object handle with the new Column created - return Column(mpPreparedStatement, index); + return Column(getStatement(), index); } // Test if the column is NULL @@ -269,18 +174,10 @@ const char* Statement::getColumnOriginName(const int aIndex) const // Return the index of the specified (potentially aliased) column name int Statement::getColumnIndex(const char* apName) const { - // Build the map of column index by name on first call - if (mColumnNames.empty()) - { - for (int i = 0; i < mColumnCount; ++i) - { - const char* pName = sqlite3_column_name(getPreparedStatement(), i); - mColumnNames[pName] = i; - } - } + auto& columns = getColumnsNames(); - const auto iIndex = mColumnNames.find(apName); - if (iIndex == mColumnNames.end()) + const auto iIndex = columns.find(apName); + if (iIndex == columns.end()) { throw SQLite::Exception("Unknown column name."); } @@ -302,33 +199,9 @@ const char * Statement::getColumnDeclaredType(const int aIndex) const } } -// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). -int Statement::getChanges() const noexcept -{ - return sqlite3_changes(mpSQLite); -} - int Statement::getBindParameterCount() const noexcept { - return sqlite3_bind_parameter_count(mpPreparedStatement.get()); -} - -// Return the numeric result code for the most recent failed API call (if any). -int Statement::getErrorCode() const noexcept -{ - return sqlite3_errcode(mpSQLite); -} - -// Return the extended numeric result code for the most recent failed API call (if any). -int Statement::getExtendedErrorCode() const noexcept -{ - return sqlite3_extended_errcode(mpSQLite); -} - -// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). -const char* Statement::getErrorMsg() const noexcept -{ - return sqlite3_errmsg(mpSQLite); + return sqlite3_bind_parameter_count(getStatement().get()); } // Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. @@ -339,30 +212,4 @@ std::string Statement::getExpandedSQL() const { return expandedString; } -// Prepare SQLite statement object and return shared pointer to this object -Statement::TStatementPtr Statement::prepareStatement() -{ - sqlite3_stmt* statement; - const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast(mQuery.size()), &statement, nullptr); - if (SQLITE_OK != ret) - { - throw SQLite::Exception(mpSQLite, ret); - } - return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt) - { - sqlite3_finalize(stmt); - }); -} - -// Return prepered statement object or throw -sqlite3_stmt* Statement::getPreparedStatement() const -{ - sqlite3_stmt* ret = mpPreparedStatement.get(); - if (ret) - { - return ret; - } - throw SQLite::Exception("Statement was not prepared."); -} - } // namespace SQLite