From 3fdd78e53b74fa1168192399124e9f1cabb7d5e3 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Sun, 24 Jul 2022 18:35:15 +0200 Subject: [PATCH 01/11] Split RowExecutor from Statement --- .gitignore | 1 + CMakeLists.txt | 2 + include/SQLiteCpp/Column.h | 2 +- include/SQLiteCpp/RowExecutor.h | 234 ++++++++++++++++++++++++++++++++ include/SQLiteCpp/Statement.h | 177 +----------------------- src/RowExecutor.cpp | 186 +++++++++++++++++++++++++ src/Statement.cpp | 175 ++---------------------- 7 files changed, 441 insertions(+), 336 deletions(-) create mode 100644 include/SQLiteCpp/RowExecutor.h create mode 100644 src/RowExecutor.cpp 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 From 2da22a7db5868fc23e8cde80dc7078778f47b833 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Sun, 24 Jul 2022 21:33:58 +0200 Subject: [PATCH 02/11] Add RowIterator and Row class fundation --- CMakeLists.txt | 2 ++ include/SQLiteCpp/Column.h | 11 +++--- include/SQLiteCpp/Row.h | 56 ++++++++++++++++++++++++++++++ include/SQLiteCpp/RowExecutor.h | 41 +++++++++++++++++----- include/SQLiteCpp/Statement.h | 60 +++++++++++++++++++++++++++++++-- src/Column.cpp | 2 +- src/Row.cpp | 41 ++++++++++++++++++++++ src/RowExecutor.cpp | 12 +++---- src/Statement.cpp | 47 ++++++++++++++++++++++++-- 9 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 include/SQLiteCpp/Row.h create mode 100644 src/Row.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c4e244f..34a4b7c0 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/Row.cpp ${PROJECT_SOURCE_DIR}/src/RowExecutor.cpp ${PROJECT_SOURCE_DIR}/src/Savepoint.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp @@ -120,6 +121,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/Row.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/RowExecutor.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Savepoint.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 77cab679..66440ebc 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -54,11 +54,7 @@ class Column * @param[in] aStmtPtr Shared pointer to the prepared SQLite Statement Object. * @param[in] aIndex Index of the column in the row of result, starting at 0 */ - explicit Column(const Statement::TStatementPtr& aStmtPtr, int aIndex); - - // default destructor: the finalization will be done by the destructor of the last shared pointer - // default copy constructor and assignment operator are perfectly suited : - // they copy the Statement::Ptr which in turn increments the reference counter. + explicit Column(const RowExecutor::TStatementPtr& aStmtPtr, int aIndex); /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) @@ -252,7 +248,7 @@ class Column } private: - Statement::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object + RowExecutor::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object int mIndex; ///< Index of the column in the row of result, starting at 0 }; @@ -283,9 +279,10 @@ T Statement::getColumns() template T Statement::getColumns(const std::integer_sequence) { - return T{Column(getStatement(), Is)...}; + return T{ Column(getStatement(), Is)... }; } #endif + } // namespace SQLite diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h new file mode 100644 index 00000000..40f204a1 --- /dev/null +++ b/include/SQLiteCpp/Row.h @@ -0,0 +1,56 @@ +/** + * @file Row.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 + +namespace SQLite +{ + + +class Row +{ +public: + Row(RowExecutor::TStatementWeakPtr apRow, std::size_t aID); + + std::size_t getRowNumber() const + { + return ID; + } + + /** + * @brief Test if the column value is NULL + * + * @param[in] aIndex Index of the column, starting at 0 + * + * @return true if the column value is NULL + * + * Throw an exception if the specified index is out of the [0, getColumnCount()) range. + */ + bool isColumnNull(const int aIndex) const; + + /** + * @brief Return a pointer to the text value (NULL terminated string) of the column. + * + * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), + * thus you must copy it before using it beyond its scope (to a std::string for instance). + */ + const char* getText(uint32_t aColumnID) const noexcept; + +private: + RowExecutor::TStatementWeakPtr mpRow; + std::size_t ID; +}; + +} // namespace SQLite diff --git a/include/SQLiteCpp/RowExecutor.h b/include/SQLiteCpp/RowExecutor.h index d3ca632d..d79a0fc8 100644 --- a/include/SQLiteCpp/RowExecutor.h +++ b/include/SQLiteCpp/RowExecutor.h @@ -1,7 +1,7 @@ /** * @file RowExecutor.h * @ingroup SQLiteCpp - * @brief TODO: + * @brief Step executor for SQLite prepared Statement Object * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) @@ -45,6 +45,11 @@ class RowExecutor /// Type to store columns names and indexes using TColumnsMap = std::map>; + RowExecutor(const RowExecutor&) = delete; + RowExecutor(RowExecutor&&) = default; + RowExecutor& operator=(const RowExecutor&) = delete; + RowExecutor& operator=(RowExecutor&&) = default; + /// Reset the statement to make it ready for a new execution. Throws an exception on error. void reset(); @@ -130,17 +135,17 @@ class RowExecutor int getChanges() const noexcept; /// Return the number of columns in the result set returned by the prepared statement - int getColumnCount() const + int getColumnCount() const noexcept { return mColumnCount; } /// true when a row has been fetched with executeStep() - bool hasRow() const + bool hasRow() const noexcept { return mbHasRow; } /// true when the last executeStep() had no more row to fetch - bool isDone() const + bool isDone() const noexcept { return mbDone; } @@ -167,7 +172,10 @@ class RowExecutor * * @return raw pointer to Statement Object */ - TStatementPtr getStatement() const noexcept; + TStatementPtr getStatement() const noexcept + { + return mpStatement; + } /** * @brief Return a prepared SQLite Statement Object. @@ -176,6 +184,19 @@ class RowExecutor * @return raw pointer to Prepared Statement Object */ sqlite3_stmt* getPreparedStatement() const; + + /** + * @brief Return a prepared SQLite Statement Object. + * + * Throw an exception if the statement object was not prepared. + * @return raw pointer to Prepared Statement Object + */ + TRowWeakPtr getExecutorWeakPtr() const + { + return mpRowExecutor; + } + + //////////////////////////////////////////////////////////////////////////// /** * @brief Check if a return code equals SQLITE_OK, else throw a SQLite::Exception with the SQLite error message @@ -222,9 +243,13 @@ class RowExecutor 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 + /// Shared Pointer to this object. + /// Allows RowIterator to execute next step + TRowPtr mpRowExecutor{}; + + 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{}; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index cd145e16..0f14af36 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -1,7 +1,7 @@ /** * @file Statement.h * @ingroup SQLiteCpp - * @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result. + * @brief A prepared SQLite Statement Object binder and Column getter. * * Copyright (c) 2012-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * @@ -541,8 +541,64 @@ class Statement : public RowExecutor /// Return the number of bind parameters in the statement int getBindParameterCount() const noexcept; + //////////////////////////////////////////////////////////////////////////// + + class RowIterator + { + public: + using iterator_category = std::input_iterator_tag; + using value_type = Row; + using reference = const Row&; + using pointer = const Row*; + using difference_type = std::ptrdiff_t; + + RowIterator() = default; + RowIterator(TStatementWeakPtr apStatement, TRowWeakPtr apRow, uint16_t aID) : + mpStatement(apStatement), mpRow(apRow), mID(aID), mRow(apStatement, aID) {} + + reference operator*() const + { + return mRow; + } + pointer operator->() const noexcept + { + return &mRow; + } + + reference operator++() noexcept + { + mRow = Row(mpStatement, ++mID); + advance(); + return mRow; + } + value_type operator++(int) + { + Row copy{ mRow }; + mRow = Row(mpStatement, ++mID); + advance(); + return copy; + } + + bool operator==(const RowIterator& aIt) const; + bool operator!=(const RowIterator& aIt) const + { + return !(*this == aIt); + } + + private: + void advance() noexcept; + + TStatementWeakPtr mpStatement{}; + TRowWeakPtr mpRow{}; + uint16_t mID{}; + Row mRow{ mpStatement, mID }; + }; + + RowIterator begin(); + RowIterator end(); + private: - std::string mQuery; //!< UTF-8 SQL Query + std::string mQuery; //!< UTF-8 SQL Query, }; diff --git a/src/Column.cpp b/src/Column.cpp index f5dc0d98..d1d15a7e 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -26,7 +26,7 @@ const int Null = SQLITE_NULL; // Encapsulation of a Column in a row of the result pointed by the prepared Statement. -Column::Column(const Statement::TStatementPtr& aStmtPtr, int aIndex) : +Column::Column(const RowExecutor::TStatementPtr& aStmtPtr, int aIndex) : mStmtPtr(aStmtPtr), mIndex(aIndex) { diff --git a/src/Row.cpp b/src/Row.cpp new file mode 100644 index 00000000..be2265b3 --- /dev/null +++ b/src/Row.cpp @@ -0,0 +1,41 @@ +/** + * @file Row.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 +{ + + + Row::Row(RowExecutor::TStatementWeakPtr apRow, std::size_t aID) : + mpRow(apRow), ID(aID) + { + } + + bool Row::isColumnNull(const int aIndex) const + { + return false; + } + + const char* Row::getText(uint32_t aColumnID) const noexcept + { + auto statement = mpRow.lock(); + + + auto pText = reinterpret_cast(sqlite3_column_text(statement.get(), aColumnID)); + return (pText ? pText : ""); + } + +} // namespace SQLite diff --git a/src/RowExecutor.cpp b/src/RowExecutor.cpp index 1c0b757e..9c053e98 100644 --- a/src/RowExecutor.cpp +++ b/src/RowExecutor.cpp @@ -1,7 +1,7 @@ /** * @file RowExecutor.cpp * @ingroup SQLiteCpp - * @brief TODO: + * @brief Step executor for SQLite prepared Statement Object * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) @@ -24,6 +24,10 @@ namespace SQLite { prepareStatement(aQuery); createColumnInfo(); + + mpRowExecutor.swap(TRowPtr(this, [](const RowExecutor* const) { + //empty destructor to make shared_ptr without ownership + })); } void SQLite::RowExecutor::prepareStatement(const std::string& aQuery) @@ -165,12 +169,6 @@ namespace SQLite 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 { diff --git a/src/Statement.cpp b/src/Statement.cpp index 5ee8cf88..038b128f 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -1,7 +1,7 @@ /** * @file Statement.cpp * @ingroup SQLiteCpp - * @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result. + * @brief A prepared SQLite Statement Object binder and Column getter. * * Copyright (c) 2012-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * @@ -22,8 +22,7 @@ namespace SQLite Statement::Statement(const Database& aDatabase, const std::string& aQuery) : - RowExecutor(aDatabase.getHandle(), aQuery), - mQuery(aQuery) + RowExecutor(aDatabase.getHandle(), aQuery), mQuery(aQuery) {} // Clears away all the bindings of a prepared statement (can be associated with #reset() above). @@ -212,4 +211,46 @@ std::string Statement::getExpandedSQL() const { return expandedString; } +Statement::RowIterator Statement::begin() +{ + reset(); + tryExecuteStep(); + return Statement::RowIterator(getStatement(), getExecutorWeakPtr(), 0); +} + +Statement::RowIterator SQLite::Statement::end() +{ + return Statement::RowIterator(); +} + +void SQLite::Statement::RowIterator::advance() noexcept +{ + if (mpRow.expired()) + return; + + auto statement = mpRow.lock(); + statement->tryExecuteStep(); + + if (statement->isDone()) + { + mpRow.reset(); + return; + } +} + +bool SQLite::Statement::RowIterator::operator==(const RowIterator& aIt) const +{ + auto left = mpRow.lock(); + auto right = aIt.mpRow.lock(); + + if (!left && !right) + return true; + + if (left != right) + return false; + + return mID == aIt.mID; +} + + } // namespace SQLite From 826a17f8d1f178e9cdf6158c1ab37e639fa97a37 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 25 Jul 2022 13:59:59 +0200 Subject: [PATCH 03/11] Move RowIterator to RowExecutor --- include/SQLiteCpp/Column.h | 2 + include/SQLiteCpp/Row.h | 19 ++-- include/SQLiteCpp/RowExecutor.h | 150 +++++++++++++++++++++++++------- include/SQLiteCpp/Statement.h | 59 +------------ src/Row.cpp | 4 +- src/RowExecutor.cpp | 53 +++++++++-- src/Statement.cpp | 41 --------- tests/Statement_test.cpp | 2 + 8 files changed, 183 insertions(+), 147 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 04de648c..92429320 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -52,6 +52,8 @@ class Column * * @param[in] aStmtPtr Shared pointer to the prepared SQLite Statement Object. * @param[in] aIndex Index of the column in the row of result, starting at 0 + * + * @throws Exception is thrown in case of error, then the Column object is NOT constructed. */ explicit Column(const RowExecutor::TStatementPtr& aStmtPtr, int aIndex); diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 40f204a1..656215a5 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -1,7 +1,7 @@ /** * @file Row.h * @ingroup SQLiteCpp - * @brief TODO: + * @brief Container for SQLite Statement Object step * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) @@ -11,18 +11,27 @@ */ #pragma once -#include +//#include +#include #include +// Forward declaration to avoid inclusion of in a header +struct sqlite3_stmt; + namespace SQLite { - +/** +* @brief CLASS IS WIP! +*/ class Row { + /// Weak pointer to SQLite Prepared Statement Object + using TStatementWeakPtr = std::weak_ptr; + public: - Row(RowExecutor::TStatementWeakPtr apRow, std::size_t aID); + Row(TStatementWeakPtr apRow, std::size_t aID); std::size_t getRowNumber() const { @@ -49,7 +58,7 @@ class Row const char* getText(uint32_t aColumnID) const noexcept; private: - RowExecutor::TStatementWeakPtr mpRow; + TStatementWeakPtr mpRow; std::size_t ID; }; diff --git a/include/SQLiteCpp/RowExecutor.h b/include/SQLiteCpp/RowExecutor.h index d79a0fc8..f2f14ca3 100644 --- a/include/SQLiteCpp/RowExecutor.h +++ b/include/SQLiteCpp/RowExecutor.h @@ -11,8 +11,8 @@ */ #pragma once - //#include - #include +#include +#include #include #include @@ -26,7 +26,21 @@ namespace SQLite extern const int OK; ///< SQLITE_OK - +/** +* @brief Base class for prepared SQLite Statement. +* +* You should use SQLite::Statement or (if you had a reson) +* inherit this class to create your own Statement executor class. +* Either way you should look at SQLite::Statement documentation +* +* Thread-safety: a RowExecutor object shall not be shared by multiple threads, because : +* 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads +* provided that no single database connection is used simultaneously in two or more threads." +* 2) the SQLite "Serialized" mode is not supported by SQLiteC++, +* because of the way it shares the underling SQLite precompiled statement +* in a custom shared pointer (See the inner class "Statement::Ptr"). +* TODO Test Serialized mode after all changes to pointers +*/ class RowExecutor { public: @@ -41,7 +55,7 @@ class RowExecutor /// Weak pointer to SQLite RowExecutor using TRowWeakPtr = std::weak_ptr; - + /// Type to store columns names and indexes using TColumnsMap = std::map>; @@ -112,25 +126,6 @@ class RowExecutor //////////////////////////////////////////////////////////////////////////// - /** - * @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; @@ -139,6 +134,13 @@ class RowExecutor { return mColumnCount; } + + /// Get columns names with theirs ids + const TColumnsMap& getColumnsNames() const + { + return mColumnNames; + } + /// true when a row has been fetched with executeStep() bool hasRow() const noexcept { @@ -161,10 +163,92 @@ class RowExecutor /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). const char* getErrorMsg() const noexcept; -protected: + //////////////////////////////////////////////////////////////////////////// + /** - * + * @brief InputIterator for statement steps. + * + * Remember that this iterator is changing state of RowExecutor. */ + class RowIterator + { + public: + using iterator_category = std::input_iterator_tag; + using value_type = Row; + using reference = const Row&; + using pointer = const Row*; + using difference_type = std::ptrdiff_t; + + RowIterator() = default; + RowIterator(TStatementWeakPtr apStatement, TRowWeakPtr apRow, uint16_t aID) : + mpStatement(apStatement), mpRow(apRow), mID(aID), mRow(apStatement, aID) {} + + reference operator*() const + { + return mRow; + } + pointer operator->() const noexcept + { + return &mRow; + } + + reference operator++() noexcept + { + mRow = Row(mpStatement, ++mID); + advance(); + return mRow; + } + value_type operator++(int) + { + Row copy{ mRow }; + mRow = Row(mpStatement, ++mID); + advance(); + return copy; + } + + bool operator==(const RowIterator& aIt) const; + bool operator!=(const RowIterator& aIt) const + { + return !(*this == aIt); + } + + private: + /// Executing next statement step + void advance() noexcept; + + TStatementWeakPtr mpStatement{}; //!< Weak pointer to SQLite Statement Object + TRowWeakPtr mpRow{}; //!< Weak pointer to RowExecutor Object + uint16_t mID{}; //!< Current row number + + /// Internal row object storage + Row mRow{ mpStatement, mID }; + }; + + /** + * @brief Start execution of SQLite Statement Object and return iterator to first row. + * + * This function calls resets SQLite Statement Object. + * + * @return RowIterator for first row of this prepared statement + * + * @throws Exception is thrown in case of error, then the RowIterator object is NOT constructed. + */ + RowIterator begin(); + + /** + * @return RowIterator to non-exisitng step + */ + RowIterator end(); + +protected: + /** + * @brief Proteced construtor to ensure that this class is only created in derived objects + * + * @param[in] apSQLite the SQLite Database Connection + * @param[in] aQuery an UTF-8 encoded query string + * + * @throws Exception is thrown in case of error, then the RowExecutor object is NOT constructed. + */ explicit RowExecutor(sqlite3* apSQLite, const std::string& aQuery); /** @@ -176,7 +260,7 @@ class RowExecutor { return mpStatement; } - + /** * @brief Return a prepared SQLite Statement Object. * @@ -184,7 +268,7 @@ class RowExecutor * @return raw pointer to Prepared Statement Object */ sqlite3_stmt* getPreparedStatement() const; - + /** * @brief Return a prepared SQLite Statement Object. * @@ -236,20 +320,20 @@ class RowExecutor 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 + sqlite3* mpSQLite{}; //!< Pointer to SQLite Database Connection Handle + TStatementPtr mpStatement{}; //!< Shared Pointer to the prepared SQLite Statement Object /// Shared Pointer to this object. /// Allows RowIterator to execute next step TRowPtr mpRowExecutor{}; - 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 + 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{}; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 0f14af36..8226c5a4 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -11,7 +11,6 @@ #pragma once #include -#include #include #include // SQLITECPP_PURE_FUNC @@ -56,7 +55,7 @@ class Statement : public RowExecutor * @param[in] aDatabase the SQLite Database Connection * @param[in] aQuery an UTF-8 encoded query string * - * Exception is thrown in case of error, then the Statement object is NOT constructed. + * @throws Exception is thrown in case of error, then the Statement object is NOT constructed. */ Statement(const Database& aDatabase, const std::string& aQuery); @@ -541,62 +540,6 @@ class Statement : public RowExecutor /// Return the number of bind parameters in the statement int getBindParameterCount() const noexcept; - //////////////////////////////////////////////////////////////////////////// - - class RowIterator - { - public: - using iterator_category = std::input_iterator_tag; - using value_type = Row; - using reference = const Row&; - using pointer = const Row*; - using difference_type = std::ptrdiff_t; - - RowIterator() = default; - RowIterator(TStatementWeakPtr apStatement, TRowWeakPtr apRow, uint16_t aID) : - mpStatement(apStatement), mpRow(apRow), mID(aID), mRow(apStatement, aID) {} - - reference operator*() const - { - return mRow; - } - pointer operator->() const noexcept - { - return &mRow; - } - - reference operator++() noexcept - { - mRow = Row(mpStatement, ++mID); - advance(); - return mRow; - } - value_type operator++(int) - { - Row copy{ mRow }; - mRow = Row(mpStatement, ++mID); - advance(); - return copy; - } - - bool operator==(const RowIterator& aIt) const; - bool operator!=(const RowIterator& aIt) const - { - return !(*this == aIt); - } - - private: - void advance() noexcept; - - TStatementWeakPtr mpStatement{}; - TRowWeakPtr mpRow{}; - uint16_t mID{}; - Row mRow{ mpStatement, mID }; - }; - - RowIterator begin(); - RowIterator end(); - private: std::string mQuery; //!< UTF-8 SQL Query, }; diff --git a/src/Row.cpp b/src/Row.cpp index be2265b3..12c7e875 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -1,7 +1,7 @@ /** * @file Row.cpp * @ingroup SQLiteCpp - * @brief TODO: + * @brief Container for SQLite Statement Object step * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) @@ -19,7 +19,7 @@ namespace SQLite { - Row::Row(RowExecutor::TStatementWeakPtr apRow, std::size_t aID) : + Row::Row(TStatementWeakPtr apRow, std::size_t aID) : mpRow(apRow), ID(aID) { } diff --git a/src/RowExecutor.cpp b/src/RowExecutor.cpp index 9c053e98..a3eec44a 100644 --- a/src/RowExecutor.cpp +++ b/src/RowExecutor.cpp @@ -26,7 +26,7 @@ namespace SQLite createColumnInfo(); mpRowExecutor.swap(TRowPtr(this, [](const RowExecutor* const) { - //empty destructor to make shared_ptr without ownership + // empty destructor to make shared_ptr without ownership })); } @@ -36,7 +36,9 @@ namespace SQLite 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); + 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); @@ -51,7 +53,7 @@ namespace SQLite { mColumnCount = sqlite3_column_count(mpStatement.get()); - + if (!mColumnNames.empty()) mColumnNames.clear(); @@ -140,11 +142,6 @@ namespace SQLite 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 { @@ -180,5 +177,45 @@ namespace SQLite throw SQLite::Exception("Statement was not prepared."); } + RowExecutor::RowIterator RowExecutor::begin() + { + reset(); + tryExecuteStep(); + return RowExecutor::RowIterator(getStatement(), getExecutorWeakPtr(), 0); + } + + RowExecutor::RowIterator RowExecutor::end() + { + return RowExecutor::RowIterator(); + } + + void RowExecutor::RowIterator::advance() noexcept + { + if (mpRow.expired()) + return; + + auto statement = mpRow.lock(); + statement->tryExecuteStep(); + + if (statement->isDone()) + { + mpRow.reset(); + return; + } + } + + bool RowExecutor::RowIterator::operator==(const RowIterator& aIt) const + { + auto left = mpRow.lock(); + auto right = aIt.mpRow.lock(); + + if (!left && !right) + return true; + + if (left != right) + return false; + + return mID == aIt.mID; + } } // namespace SQLite diff --git a/src/Statement.cpp b/src/Statement.cpp index 038b128f..e9101f1e 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -211,46 +211,5 @@ std::string Statement::getExpandedSQL() const { return expandedString; } -Statement::RowIterator Statement::begin() -{ - reset(); - tryExecuteStep(); - return Statement::RowIterator(getStatement(), getExecutorWeakPtr(), 0); -} - -Statement::RowIterator SQLite::Statement::end() -{ - return Statement::RowIterator(); -} - -void SQLite::Statement::RowIterator::advance() noexcept -{ - if (mpRow.expired()) - return; - - auto statement = mpRow.lock(); - statement->tryExecuteStep(); - - if (statement->isDone()) - { - mpRow.reset(); - return; - } -} - -bool SQLite::Statement::RowIterator::operator==(const RowIterator& aIt) const -{ - auto left = mpRow.lock(); - auto right = aIt.mpRow.lock(); - - if (!left && !right) - return true; - - if (left != right) - return false; - - return mID == aIt.mID; -} - } // namespace SQLite diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 96e5ec1f..c6b187e1 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -98,6 +98,7 @@ TEST(Statement, invalid) EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result } +/* TODO: Re-enable this test #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600) SQLite::Statement StatementBuilder(SQLite::Database& aDb, const char* apQuery) @@ -141,6 +142,7 @@ TEST(Statement, moveConstructor) } #endif +*/ TEST(Statement, executeStep) { From 4117377db83f398c4d7be4085fe5a597299456c4 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 25 Jul 2022 20:26:26 +0200 Subject: [PATCH 04/11] Rename RowExecutor to StatementExecutor --- CMakeLists.txt | 4 +- include/SQLiteCpp/Column.h | 4 +- include/SQLiteCpp/Statement.h | 4 +- .../{RowExecutor.h => StatementExecutor.h} | 30 ++++++------- src/Column.cpp | 2 +- src/Statement.cpp | 2 +- ...{RowExecutor.cpp => StatementExecutor.cpp} | 44 +++++++++---------- 7 files changed, 45 insertions(+), 45 deletions(-) rename include/SQLiteCpp/{RowExecutor.h => StatementExecutor.h} (92%) rename src/{RowExecutor.cpp => StatementExecutor.cpp} (79%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a4b7c0..535cf760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,9 +106,9 @@ set(SQLITECPP_SRC ${PROJECT_SOURCE_DIR}/src/Database.cpp ${PROJECT_SOURCE_DIR}/src/Exception.cpp ${PROJECT_SOURCE_DIR}/src/Row.cpp - ${PROJECT_SOURCE_DIR}/src/RowExecutor.cpp ${PROJECT_SOURCE_DIR}/src/Savepoint.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp + ${PROJECT_SOURCE_DIR}/src/StatementExecutor.cpp ${PROJECT_SOURCE_DIR}/src/Transaction.cpp ) source_group(src FILES ${SQLITECPP_SRC}) @@ -122,9 +122,9 @@ set(SQLITECPP_INC ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Database.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Exception.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Row.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/StatementExecutor.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/VariadicBind.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/ExecuteMany.h diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 92429320..2d83e338 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -55,7 +55,7 @@ class Column * * @throws Exception is thrown in case of error, then the Column object is NOT constructed. */ - explicit Column(const RowExecutor::TStatementPtr& aStmtPtr, int aIndex); + explicit Column(const StatementExecutor::TStatementPtr& aStmtPtr, int aIndex); /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) @@ -226,7 +226,7 @@ class Column } private: - RowExecutor::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object + StatementExecutor::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object int mIndex; ///< Index of the column in the row of result, starting at 0 }; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 8226c5a4..433afdb9 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -10,7 +10,7 @@ */ #pragma once -#include +#include #include #include // SQLITECPP_PURE_FUNC @@ -46,7 +46,7 @@ class Column; * because of the way it shares the underling SQLite precompiled statement * in a custom shared pointer (See the inner class "Statement::Ptr"). */ -class Statement : public RowExecutor +class Statement : public StatementExecutor { public: /** diff --git a/include/SQLiteCpp/RowExecutor.h b/include/SQLiteCpp/StatementExecutor.h similarity index 92% rename from include/SQLiteCpp/RowExecutor.h rename to include/SQLiteCpp/StatementExecutor.h index f2f14ca3..52fa8856 100644 --- a/include/SQLiteCpp/RowExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -1,5 +1,5 @@ /** - * @file RowExecutor.h + * @file StatementExecutor.h * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * @@ -33,7 +33,7 @@ extern const int OK; ///< SQLITE_OK * inherit this class to create your own Statement executor class. * Either way you should look at SQLite::Statement documentation * -* Thread-safety: a RowExecutor object shall not be shared by multiple threads, because : +* Thread-safety: a StatementExecutor object shall not be shared by multiple threads, because : * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads * provided that no single database connection is used simultaneously in two or more threads." * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, @@ -41,7 +41,7 @@ extern const int OK; ///< SQLITE_OK * in a custom shared pointer (See the inner class "Statement::Ptr"). * TODO Test Serialized mode after all changes to pointers */ -class RowExecutor +class StatementExecutor { public: /// Shared pointer to SQLite Prepared Statement Object @@ -50,19 +50,19 @@ class RowExecutor /// Weak pointer to SQLite Prepared Statement Object using TStatementWeakPtr = std::weak_ptr; - /// Shared pointer to SQLite RowExecutor - using TRowPtr = std::shared_ptr; + /// Shared pointer to SQLite StatementExecutor + using TRowPtr = std::shared_ptr; - /// Weak pointer to SQLite RowExecutor - using TRowWeakPtr = std::weak_ptr; + /// Weak pointer to SQLite StatementExecutor + using TRowWeakPtr = std::weak_ptr; /// Type to store columns names and indexes using TColumnsMap = std::map>; - RowExecutor(const RowExecutor&) = delete; - RowExecutor(RowExecutor&&) = default; - RowExecutor& operator=(const RowExecutor&) = delete; - RowExecutor& operator=(RowExecutor&&) = default; + StatementExecutor(const StatementExecutor&) = delete; + StatementExecutor(StatementExecutor&&) = default; + StatementExecutor& operator=(const StatementExecutor&) = delete; + StatementExecutor& operator=(StatementExecutor&&) = default; /// Reset the statement to make it ready for a new execution. Throws an exception on error. void reset(); @@ -168,7 +168,7 @@ class RowExecutor /** * @brief InputIterator for statement steps. * - * Remember that this iterator is changing state of RowExecutor. + * Remember that this iterator is changing state of StatementExecutor. */ class RowIterator { @@ -217,7 +217,7 @@ class RowExecutor void advance() noexcept; TStatementWeakPtr mpStatement{}; //!< Weak pointer to SQLite Statement Object - TRowWeakPtr mpRow{}; //!< Weak pointer to RowExecutor Object + TRowWeakPtr mpRow{}; //!< Weak pointer to StatementExecutor Object uint16_t mID{}; //!< Current row number /// Internal row object storage @@ -247,9 +247,9 @@ class RowExecutor * @param[in] apSQLite the SQLite Database Connection * @param[in] aQuery an UTF-8 encoded query string * - * @throws Exception is thrown in case of error, then the RowExecutor object is NOT constructed. + * @throws Exception is thrown in case of error, then the StatementExecutor object is NOT constructed. */ - explicit RowExecutor(sqlite3* apSQLite, const std::string& aQuery); + explicit StatementExecutor(sqlite3* apSQLite, const std::string& aQuery); /** * @brief Return a std::shared_ptr with SQLite Statement Object. diff --git a/src/Column.cpp b/src/Column.cpp index de977ab0..cd1e1bf1 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -26,7 +26,7 @@ const int Null = SQLITE_NULL; // Encapsulation of a Column in a row of the result pointed by the prepared Statement. -Column::Column(const RowExecutor::TStatementPtr& aStmtPtr, int aIndex) : +Column::Column(const StatementExecutor::TStatementPtr& aStmtPtr, int aIndex) : mStmtPtr(aStmtPtr), mIndex(aIndex) { diff --git a/src/Statement.cpp b/src/Statement.cpp index e9101f1e..b12618ff 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -22,7 +22,7 @@ namespace SQLite Statement::Statement(const Database& aDatabase, const std::string& aQuery) : - RowExecutor(aDatabase.getHandle(), aQuery), mQuery(aQuery) + StatementExecutor(aDatabase.getHandle(), aQuery), mQuery(aQuery) {} // Clears away all the bindings of a prepared statement (can be associated with #reset() above). diff --git a/src/RowExecutor.cpp b/src/StatementExecutor.cpp similarity index 79% rename from src/RowExecutor.cpp rename to src/StatementExecutor.cpp index a3eec44a..e4c793f9 100644 --- a/src/RowExecutor.cpp +++ b/src/StatementExecutor.cpp @@ -1,5 +1,5 @@ /** - * @file RowExecutor.cpp + * @file StatementExecutor.cpp * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * @@ -9,7 +9,7 @@ * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * or copy at http://opensource.org/licenses/MIT) */ -#include +#include #include @@ -19,18 +19,18 @@ namespace SQLite { - RowExecutor::RowExecutor(sqlite3* apSQLite, const std::string& aQuery) + StatementExecutor::StatementExecutor(sqlite3* apSQLite, const std::string& aQuery) : mpSQLite(apSQLite) { prepareStatement(aQuery); createColumnInfo(); - mpRowExecutor.swap(TRowPtr(this, [](const RowExecutor* const) { + mpRowExecutor.swap(TRowPtr(this, [](const StatementExecutor* const) { // empty destructor to make shared_ptr without ownership })); } - void SQLite::RowExecutor::prepareStatement(const std::string& aQuery) + void SQLite::StatementExecutor::prepareStatement(const std::string& aQuery) { if (!mpSQLite) throw SQLite::Exception("Can't create statement without valid database connection"); @@ -49,7 +49,7 @@ namespace SQLite }); } - void SQLite::RowExecutor::createColumnInfo() + void SQLite::StatementExecutor::createColumnInfo() { mColumnCount = sqlite3_column_count(mpStatement.get()); @@ -66,13 +66,13 @@ namespace SQLite } // Reset the statement to make it ready for a new execution (see also #clearBindings() bellow) - void RowExecutor::reset() + void StatementExecutor::reset() { const int ret = tryReset(); check(ret); } - int RowExecutor::tryReset() noexcept + int StatementExecutor::tryReset() noexcept { mbHasRow = false; mbDone = false; @@ -80,7 +80,7 @@ namespace SQLite } // Execute a step of the query to fetch one row of results - bool RowExecutor::executeStep() + bool StatementExecutor::executeStep() { const int ret = tryExecuteStep(); if ((SQLITE_ROW != ret) && (SQLITE_DONE != ret)) // on row or no (more) row ready, else it's a problem @@ -99,7 +99,7 @@ namespace SQLite } // Execute a one-step query with no expected result, and return the number of changes. - int RowExecutor::exec() + int StatementExecutor::exec() { const int ret = tryExecuteStep(); if (SQLITE_DONE != ret) // the statement has finished executing successfully @@ -122,7 +122,7 @@ namespace SQLite return sqlite3_changes(mpSQLite); } - int RowExecutor::tryExecuteStep() noexcept + int StatementExecutor::tryExecuteStep() noexcept { if (mbDone) { @@ -143,31 +143,31 @@ namespace SQLite } // Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). - int RowExecutor::getChanges() const noexcept + int StatementExecutor::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 + int StatementExecutor::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 + int StatementExecutor::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 + const char* StatementExecutor::getErrorMsg() const noexcept { return sqlite3_errmsg(mpSQLite); } // Return prepered SQLite statement object or throw - sqlite3_stmt* RowExecutor::getPreparedStatement() const + sqlite3_stmt* StatementExecutor::getPreparedStatement() const { sqlite3_stmt* ret = mpStatement.get(); if (ret) @@ -177,19 +177,19 @@ namespace SQLite throw SQLite::Exception("Statement was not prepared."); } - RowExecutor::RowIterator RowExecutor::begin() + StatementExecutor::RowIterator StatementExecutor::begin() { reset(); tryExecuteStep(); - return RowExecutor::RowIterator(getStatement(), getExecutorWeakPtr(), 0); + return StatementExecutor::RowIterator(getStatement(), getExecutorWeakPtr(), 0); } - RowExecutor::RowIterator RowExecutor::end() + StatementExecutor::RowIterator StatementExecutor::end() { - return RowExecutor::RowIterator(); + return StatementExecutor::RowIterator(); } - void RowExecutor::RowIterator::advance() noexcept + void StatementExecutor::RowIterator::advance() noexcept { if (mpRow.expired()) return; @@ -204,7 +204,7 @@ namespace SQLite } } - bool RowExecutor::RowIterator::operator==(const RowIterator& aIt) const + bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const { auto left = mpRow.lock(); auto right = aIt.mpRow.lock(); From b72479ad8aa25eb81575e1bef151c209038e9238 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 25 Jul 2022 21:46:17 +0200 Subject: [PATCH 05/11] Add StatementPtr - better structure for Statement shared_ptr --- CMakeLists.txt | 2 + include/SQLiteCpp/Column.h | 30 +------- include/SQLiteCpp/Database.h | 1 + include/SQLiteCpp/Row.h | 11 +-- include/SQLiteCpp/Statement.h | 28 ++++--- include/SQLiteCpp/StatementExecutor.h | 48 +++++------- include/SQLiteCpp/StatementPtr.h | 75 +++++++++++++++++++ src/Column.cpp | 10 +-- src/Row.cpp | 12 +-- src/StatementExecutor.cpp | 102 +++++++++++--------------- src/StatementPtr.cpp | 63 ++++++++++++++++ tests/Statement_test.cpp | 2 - 12 files changed, 235 insertions(+), 149 deletions(-) create mode 100644 include/SQLiteCpp/StatementPtr.h create mode 100644 src/StatementPtr.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 535cf760..23004e29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ set(SQLITECPP_SRC ${PROJECT_SOURCE_DIR}/src/Savepoint.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp ${PROJECT_SOURCE_DIR}/src/StatementExecutor.cpp + ${PROJECT_SOURCE_DIR}/src/StatementPtr.cpp ${PROJECT_SOURCE_DIR}/src/Transaction.cpp ) source_group(src FILES ${SQLITECPP_SRC}) @@ -125,6 +126,7 @@ set(SQLITECPP_INC ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Savepoint.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/StatementExecutor.h + ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/StatementPtr.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/VariadicBind.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/ExecuteMany.h diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 2d83e338..f510132e 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -10,14 +10,12 @@ */ #pragma once -#include -#include +#include +#include #include #include -// Forward declarations to avoid inclusion of in a header -struct sqlite3_stmt; namespace SQLite { @@ -55,7 +53,7 @@ class Column * * @throws Exception is thrown in case of error, then the Column object is NOT constructed. */ - explicit Column(const StatementExecutor::TStatementPtr& aStmtPtr, int aIndex); + explicit Column(const StatementPtr::TStatementPtr& aStmtPtr, int aIndex); /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) @@ -226,7 +224,7 @@ class Column } private: - StatementExecutor::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object + StatementPtr::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object int mIndex; ///< Index of the column in the row of result, starting at 0 }; @@ -242,25 +240,5 @@ class Column */ std::ostream& operator<<(std::ostream& aStream, const Column& aColumn); -#if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) // c++14: Visual Studio 2015 - -// Create an instance of T from the first N columns, see declaration in Statement.h for full details -template -T Statement::getColumns() -{ - checkRow(); - checkIndex(N - 1); - return getColumns(std::make_integer_sequence{}); -} - -// Helper function called by getColums -template -T Statement::getColumns(const std::integer_sequence) -{ - return T{ Column(getStatement(), Is)... }; -} - -#endif - } // namespace SQLite diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 3f55e504..607e770c 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -10,6 +10,7 @@ */ #pragma once +#include #include // c++17: MinGW GCC version > 8 diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 656215a5..7729d50a 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -11,13 +11,11 @@ */ #pragma once -//#include +#include #include #include -// Forward declaration to avoid inclusion of in a header -struct sqlite3_stmt; namespace SQLite { @@ -27,11 +25,8 @@ namespace SQLite */ class Row { - /// Weak pointer to SQLite Prepared Statement Object - using TStatementWeakPtr = std::weak_ptr; - public: - Row(TStatementWeakPtr apRow, std::size_t aID); + Row(TRowWeakPtr apStatement, std::size_t aID); std::size_t getRowNumber() const { @@ -58,7 +53,7 @@ class Row const char* getText(uint32_t aColumnID) const noexcept; private: - TStatementWeakPtr mpRow; + TRowWeakPtr mpStatement; std::size_t ID; }; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 433afdb9..9fd6028c 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -17,7 +17,6 @@ #include #include #include -#include namespace SQLite @@ -59,18 +58,13 @@ class Statement : public StatementExecutor */ Statement(const Database& aDatabase, const std::string& aQuery); - /** - * @brief Move an SQLite statement. - * - * @param[in] aStatement Statement to move - */ - Statement(Statement&& aStatement) noexcept = default; - Statement& operator=(Statement&& aStatement) noexcept = default; - // Statement is non-copyable Statement(const Statement&) = delete; Statement& operator=(const Statement&) = delete; + Statement(Statement&& aStatement) = default; + Statement& operator=(Statement&& aStatement) = default; + /// Finalize and unregister the SQL query from the SQLite Database Connection. /// The finalization will be done by the destructor of the last shared pointer ~Statement() = default; @@ -440,15 +434,27 @@ class Statement : public StatementExecutor * @note Requires std=C++14 */ template - T getColumns(); + T getColumns() + { + checkRow(); + checkIndex(N - 1); + return getColumns(std::make_integer_sequence{}); + } private: /** * @brief Helper function used by getColumns to expand an integer_sequence used to generate * the required Column objects + * + * @note Requires std=C++14 + * + * @return Column object for each column in statement */ template - T getColumns(const std::integer_sequence); + T getColumns(const std::integer_sequence) + { + return T{ Column(getStatement(), Is)... }; + } public: #endif diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h index 52fa8856..3471b976 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -3,23 +3,22 @@ * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) - * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-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 #include #include -// Forward declaration to avoid inclusion of in a header -struct sqlite3_stmt; namespace SQLite { @@ -44,24 +43,13 @@ extern const int OK; ///< SQLITE_OK class StatementExecutor { 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 StatementExecutor - using TRowPtr = std::shared_ptr; - - /// Weak pointer to SQLite StatementExecutor - using TRowWeakPtr = std::weak_ptr; - /// Type to store columns names and indexes using TColumnsMap = std::map>; StatementExecutor(const StatementExecutor&) = delete; - StatementExecutor(StatementExecutor&&) = default; StatementExecutor& operator=(const StatementExecutor&) = delete; + + StatementExecutor(StatementExecutor&&) = default; StatementExecutor& operator=(StatementExecutor&&) = default; /// Reset the statement to make it ready for a new execution. Throws an exception on error. @@ -180,8 +168,8 @@ class StatementExecutor using difference_type = std::ptrdiff_t; RowIterator() = default; - RowIterator(TStatementWeakPtr apStatement, TRowWeakPtr apRow, uint16_t aID) : - mpStatement(apStatement), mpRow(apRow), mID(aID), mRow(apStatement, aID) {} + RowIterator(TRowWeakPtr apStatement, uint16_t aID) : + mpStatement(apStatement), mID(aID), mRow(apStatement, aID) {} reference operator*() const { @@ -216,8 +204,7 @@ class StatementExecutor /// Executing next statement step void advance() noexcept; - TStatementWeakPtr mpStatement{}; //!< Weak pointer to SQLite Statement Object - TRowWeakPtr mpRow{}; //!< Weak pointer to StatementExecutor Object + TRowWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object uint16_t mID{}; //!< Current row number /// Internal row object storage @@ -256,9 +243,9 @@ class StatementExecutor * * @return raw pointer to Statement Object */ - TStatementPtr getStatement() const noexcept + StatementPtr::TStatementPtr getStatement() const noexcept { - return mpStatement; + return mpStatement->mpStatement; } /** @@ -277,7 +264,7 @@ class StatementExecutor */ TRowWeakPtr getExecutorWeakPtr() const { - return mpRowExecutor; + return mpStatement; } //////////////////////////////////////////////////////////////////////////// @@ -291,7 +278,7 @@ class StatementExecutor { if (SQLite::OK != aRet) { - throw SQLite::Exception(mpSQLite, aRet); + throw SQLite::Exception(mpStatement->mpConnection, aRet); } } @@ -318,18 +305,17 @@ class StatementExecutor } 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 + // xD + bool checkReturnCode(int aReturnCode) const; + // xD + bool checkReturnCode(int aReturnCode, int aErrorCode) const; /// Shared Pointer to this object. /// Allows RowIterator to execute next step - TRowPtr mpRowExecutor{}; + TRowPtr mpStatement{}; 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() diff --git a/include/SQLiteCpp/StatementPtr.h b/include/SQLiteCpp/StatementPtr.h new file mode 100644 index 00000000..cfbeb6bd --- /dev/null +++ b/include/SQLiteCpp/StatementPtr.h @@ -0,0 +1,75 @@ +/** + * @file StatementPtr.h + * @ingroup SQLiteCpp + * @brief Pointer for prepared SQLite Statement Object + * + * Copyright (c) 2022 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 + +// Forward declaration to avoid inclusion of in a header +struct sqlite3; +struct sqlite3_stmt; + +namespace SQLite +{ + + +/** +* @brief Container for SQLite Statement pointer. +* +* You should never create this object unless you are expanding SQLiteCPP API. +*/ +struct StatementPtr +{ + /** + * @brief Don't create this object unless you are expanding SQLiteCPP API. + * + * @param[in] apSQLite the SQLite Database Connection + * @param[in] aQuery an UTF-8 encoded query string + * + * @throws Exception is thrown in case of error, then the StatementPtr object is NOT constructed. + */ + StatementPtr(sqlite3* apSQLite, const std::string& aQuery); + + /// Shared pointer to SQLite prepared Statement Object + using TStatementPtr = std::shared_ptr; + + sqlite3* const mpConnection; //!< Pointer to SQLite Database Connection Handle + TStatementPtr const mpStatement; //!< Shared Pointer to the prepared SQLite Statement Object + std::size_t mCurrentStep = 0; //!< Current step of prepared Statement Object + + /// Resets SQLite Statement Object + int reset() noexcept; + + /// Execute next step of SQLite Statement Object + int step() noexcept; + + /** + * @brief Returns pointer to prepared SQLite Statement Object. + * Use this ONLY on sqlite3 function! + * + * @return Pointer to SQLite Statement Object + */ + sqlite3_stmt* getPreparedStatement() const; + +private: + /// Create prepared SQLite Statement Object + TStatementPtr prepareStatement(sqlite3* apConnection, const std::string& aQuery) const; +}; + + +/// Shared pointer to SQLite StatementPtr +using TRowPtr = std::shared_ptr; + +/// Weak pointer to SQLite StatementPtr +using TRowWeakPtr = std::weak_ptr; + + +} // namespace SQLite diff --git a/src/Column.cpp b/src/Column.cpp index cd1e1bf1..3d1201f6 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -10,9 +10,9 @@ */ #include -#include +#include -#include +#include namespace SQLite @@ -26,7 +26,7 @@ const int Null = SQLITE_NULL; // Encapsulation of a Column in a row of the result pointed by the prepared Statement. -Column::Column(const StatementExecutor::TStatementPtr& aStmtPtr, int aIndex) : +Column::Column(const StatementPtr::TStatementPtr& aStmtPtr, int aIndex) : mStmtPtr(aStmtPtr), mIndex(aIndex) { @@ -78,7 +78,7 @@ double Column::getDouble() const noexcept const char* Column::getText(const char* apDefaultValue /* = "" */) const noexcept { auto pText = reinterpret_cast(sqlite3_column_text(mStmtPtr.get(), mIndex)); - return (pText?pText:apDefaultValue); + return (pText ? pText : apDefaultValue); } // Return a pointer to the blob value (*not* NULL terminated) of the column specified by its index starting at 0 @@ -92,7 +92,7 @@ std::string Column::getString() const { // Note: using sqlite3_column_blob and not sqlite3_column_text // - no need for sqlite3_column_text to add a \0 on the end, as we're getting the bytes length directly - auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); // SQLite docs: "The safest policy is to invokeā€¦ sqlite3_column_blob() followed by sqlite3_column_bytes()" // Note: std::string is ok to pass nullptr as first arg, if length is 0 diff --git a/src/Row.cpp b/src/Row.cpp index 12c7e875..222d9c57 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -19,22 +19,24 @@ namespace SQLite { - Row::Row(TStatementWeakPtr apRow, std::size_t aID) : - mpRow(apRow), ID(aID) + Row::Row(TRowWeakPtr apStatement, std::size_t aID) : + mpStatement(apStatement), ID(aID) { } bool Row::isColumnNull(const int aIndex) const { - return false; + auto statement = mpStatement.lock(); + + return (SQLITE_NULL == sqlite3_column_type(statement->getPreparedStatement(), aIndex)); } const char* Row::getText(uint32_t aColumnID) const noexcept { - auto statement = mpRow.lock(); + auto statement = mpStatement.lock(); - auto pText = reinterpret_cast(sqlite3_column_text(statement.get(), aColumnID)); + auto pText = reinterpret_cast(sqlite3_column_text(statement->getPreparedStatement(), aColumnID)); return (pText ? pText : ""); } diff --git a/src/StatementExecutor.cpp b/src/StatementExecutor.cpp index e4c793f9..d9d20744 100644 --- a/src/StatementExecutor.cpp +++ b/src/StatementExecutor.cpp @@ -3,8 +3,7 @@ * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) - * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-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) @@ -19,39 +18,15 @@ namespace SQLite { - StatementExecutor::StatementExecutor(sqlite3* apSQLite, const std::string& aQuery) - : mpSQLite(apSQLite) + StatementExecutor::StatementExecutor(sqlite3* apSQLite, const std::string& aQuery) : + mpStatement(std::make_shared(apSQLite, aQuery)) { - prepareStatement(aQuery); createColumnInfo(); - - mpRowExecutor.swap(TRowPtr(this, [](const StatementExecutor* const) { - // empty destructor to make shared_ptr without ownership - })); } - void SQLite::StatementExecutor::prepareStatement(const std::string& aQuery) + void StatementExecutor::createColumnInfo() { - 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::StatementExecutor::createColumnInfo() - { - mColumnCount = sqlite3_column_count(mpStatement.get()); + mColumnCount = sqlite3_column_count(mpStatement->getPreparedStatement()); if (!mColumnNames.empty()) @@ -65,6 +40,24 @@ namespace SQLite } } + bool StatementExecutor::checkReturnCode(int aReturnCode) const + { + if (aReturnCode == getErrorCode()) + { + throw SQLite::Exception(mpStatement->mpConnection, aReturnCode); + } + return true; + } + + bool StatementExecutor::checkReturnCode(int aReturnCode, int aErrorCode) const + { + if (aReturnCode == aErrorCode) + { + throw SQLite::Exception(mpStatement->mpConnection, aReturnCode); + } + return true; + } + // Reset the statement to make it ready for a new execution (see also #clearBindings() bellow) void StatementExecutor::reset() { @@ -76,7 +69,7 @@ namespace SQLite { mbHasRow = false; mbDone = false; - return sqlite3_reset(mpStatement.get()); + return mpStatement->reset(); } // Execute a step of the query to fetch one row of results @@ -85,11 +78,7 @@ namespace SQLite 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 + if (checkReturnCode(ret)) { throw SQLite::Exception("Statement needs to be reseted", ret); } @@ -108,18 +97,14 @@ namespace SQLite { throw SQLite::Exception("exec() does not expect results. Use executeStep."); } - else if (ret == sqlite3_errcode(mpSQLite)) - { - throw SQLite::Exception(mpSQLite, ret); - } - else + else if (checkReturnCode(ret)) { 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); + return getChanges(); } int StatementExecutor::tryExecuteStep() noexcept @@ -129,7 +114,7 @@ namespace SQLite return SQLITE_MISUSE; // Statement needs to be reseted ! } - const int ret = sqlite3_step(mpStatement.get()); + const auto ret = mpStatement->step(); if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it { mbHasRow = true; @@ -145,43 +130,38 @@ namespace SQLite // Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). int StatementExecutor::getChanges() const noexcept { - return sqlite3_changes(mpSQLite); + return sqlite3_changes(mpStatement->mpConnection); } // Return the numeric result code for the most recent failed API call (if any). int StatementExecutor::getErrorCode() const noexcept { - return sqlite3_errcode(mpSQLite); + return sqlite3_errcode(mpStatement->mpConnection); } // Return the extended numeric result code for the most recent failed API call (if any). int StatementExecutor::getExtendedErrorCode() const noexcept { - return sqlite3_extended_errcode(mpSQLite); + return sqlite3_extended_errcode(mpStatement->mpConnection); } // Return UTF-8 encoded English language explanation of the most recent failed API call (if any). const char* StatementExecutor::getErrorMsg() const noexcept { - return sqlite3_errmsg(mpSQLite); + return sqlite3_errmsg(mpStatement->mpConnection); } // Return prepered SQLite statement object or throw sqlite3_stmt* StatementExecutor::getPreparedStatement() const { - sqlite3_stmt* ret = mpStatement.get(); - if (ret) - { - return ret; - } - throw SQLite::Exception("Statement was not prepared."); + return mpStatement->getPreparedStatement(); } StatementExecutor::RowIterator StatementExecutor::begin() { reset(); tryExecuteStep(); - return StatementExecutor::RowIterator(getStatement(), getExecutorWeakPtr(), 0); + return StatementExecutor::RowIterator(mpStatement, 0); } StatementExecutor::RowIterator StatementExecutor::end() @@ -191,23 +171,23 @@ namespace SQLite void StatementExecutor::RowIterator::advance() noexcept { - if (mpRow.expired()) + if (mpStatement.expired()) return; - auto statement = mpRow.lock(); - statement->tryExecuteStep(); + auto statement = mpStatement.lock(); + auto ret = statement->step(); - if (statement->isDone()) + if (SQLITE_ROW != ret) { - mpRow.reset(); + mpStatement = TRowWeakPtr{}; return; } } bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const { - auto left = mpRow.lock(); - auto right = aIt.mpRow.lock(); + auto left = mpStatement.lock(); + auto right = aIt.mpStatement.lock(); if (!left && !right) return true; diff --git a/src/StatementPtr.cpp b/src/StatementPtr.cpp new file mode 100644 index 00000000..6f1edca6 --- /dev/null +++ b/src/StatementPtr.cpp @@ -0,0 +1,63 @@ +/** + * @file StatementPtr.cpp + * @ingroup SQLiteCpp + * @brief Pointer for prepared SQLite Statement Object + * + * Copyright (c) 2022 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 +{ + + + StatementPtr::StatementPtr(sqlite3* apSQLite, const std::string& aQuery) : + mpConnection(apSQLite), mpStatement(prepareStatement(apSQLite, aQuery)) + {} + + sqlite3_stmt* StatementPtr::getPreparedStatement() const + { + return mpStatement.get(); + } + + int StatementPtr::reset() noexcept + { + mCurrentStep = 0; + return sqlite3_reset(mpStatement.get()); + } + + int StatementPtr::step() noexcept + { + ++mCurrentStep; + return sqlite3_step(mpStatement.get()); + } + + StatementPtr::TStatementPtr StatementPtr::prepareStatement(sqlite3* apConnection, const std::string& aQuery) const + { + if (!apConnection) + throw SQLite::Exception("Can't create statement without valid database connection"); + + sqlite3_stmt* statement; + const int ret = sqlite3_prepare_v2(apConnection, aQuery.c_str(), + static_cast(aQuery.size()), &statement, nullptr); + + if (SQLITE_OK != ret) + { + throw SQLite::Exception(apConnection, ret); + } + + return TStatementPtr(statement, [](sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + }); + } + + +} // namespace SQLite diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index c6b187e1..96e5ec1f 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -98,7 +98,6 @@ TEST(Statement, invalid) EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result } -/* TODO: Re-enable this test #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600) SQLite::Statement StatementBuilder(SQLite::Database& aDb, const char* apQuery) @@ -142,7 +141,6 @@ TEST(Statement, moveConstructor) } #endif -*/ TEST(Statement, executeStep) { From 12a38864e195c18a0e201527db7f4052e1b7248e Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 1 Aug 2022 17:17:47 +0200 Subject: [PATCH 06/11] Clean new classes code --- include/SQLiteCpp/Column.h | 12 +++- include/SQLiteCpp/Row.h | 6 +- include/SQLiteCpp/Statement.h | 7 +-- include/SQLiteCpp/StatementExecutor.h | 79 ++++++++++++++------------- include/SQLiteCpp/StatementPtr.h | 17 +++--- src/Column.cpp | 2 +- src/Row.cpp | 8 +-- src/Statement.cpp | 44 +++++++-------- src/StatementExecutor.cpp | 50 +++++++---------- src/StatementPtr.cpp | 7 ++- 10 files changed, 114 insertions(+), 118 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index f510132e..61786a9b 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -53,7 +53,13 @@ class Column * * @throws Exception is thrown in case of error, then the Column object is NOT constructed. */ - explicit Column(const StatementPtr::TStatementPtr& aStmtPtr, int aIndex); + explicit Column(const StatementPtr::TRawStatementPtr& aStmtPtr, int aIndex); + + Column(const Column&) = delete; + Column& operator=(const Column&) = delete; + + Column(Column&& aColumn) = default; + Column& operator=(Column&& aColumn) = default; /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) @@ -224,8 +230,8 @@ class Column } private: - StatementPtr::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object - int mIndex; ///< Index of the column in the row of result, starting at 0 + StatementPtr::TRawStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object + int mIndex; ///< Index of the column in the row of result, starting at 0 }; /** diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 7729d50a..fd3f783c 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Container for SQLite Statement Object step * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -26,7 +26,7 @@ namespace SQLite class Row { public: - Row(TRowWeakPtr apStatement, std::size_t aID); + Row(TStatementWeakPtr apStatement, std::size_t aID); std::size_t getRowNumber() const { @@ -53,7 +53,7 @@ class Row const char* getText(uint32_t aColumnID) const noexcept; private: - TRowWeakPtr mpStatement; + TStatementWeakPtr mpStatement; std::size_t ID; }; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 9fd6028c..cef8a677 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -58,7 +58,6 @@ class Statement : public StatementExecutor */ Statement(const Database& aDatabase, const std::string& aQuery); - // Statement is non-copyable Statement(const Statement&) = delete; Statement& operator=(const Statement&) = delete; @@ -453,7 +452,7 @@ class Statement : public StatementExecutor template T getColumns(const std::integer_sequence) { - return T{ Column(getStatement(), Is)... }; + return T{ Column(getStatementPtr(), Is)... }; } public: @@ -530,7 +529,7 @@ class Statement : public StatementExecutor * - the statement is not a SELECT query * - the column at aIndex is not a table column but an expression or subquery */ - const char * getColumnDeclaredType(const int aIndex) const; + const char* getColumnDeclaredType(const int aIndex) const; //////////////////////////////////////////////////////////////////////////// @@ -540,7 +539,7 @@ class Statement : public StatementExecutor return mQuery; } - // Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. + /// Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. std::string getExpandedSQL() const; /// Return the number of bind parameters in the statement diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h index 3471b976..dc7b1b99 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -3,6 +3,7 @@ * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2012-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -38,7 +39,7 @@ extern const int OK; ///< SQLITE_OK * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, * because of the way it shares the underling SQLite precompiled statement * in a custom shared pointer (See the inner class "Statement::Ptr"). -* TODO Test Serialized mode after all changes to pointers +* TODO: Test Serialized mode after all changes to pointers */ class StatementExecutor { @@ -52,7 +53,12 @@ class StatementExecutor StatementExecutor(StatementExecutor&&) = default; StatementExecutor& operator=(StatementExecutor&&) = default; - /// Reset the statement to make it ready for a new execution. Throws an exception on error. + /** + * @brief Reset the statement to make it ready for a new execution. + * This doesn't clear bindings! + * + * @throws SQLite::Exception in case of error + */ void reset(); /// Reset the statement. Returns the sqlite result code instead of throwing an exception on error. @@ -124,7 +130,7 @@ class StatementExecutor } /// Get columns names with theirs ids - const TColumnsMap& getColumnsNames() const + const TColumnsMap& getColumnsNames() const noexcept { return mColumnNames; } @@ -168,10 +174,10 @@ class StatementExecutor using difference_type = std::ptrdiff_t; RowIterator() = default; - RowIterator(TRowWeakPtr apStatement, uint16_t aID) : + RowIterator(TStatementWeakPtr apStatement, uint16_t aID) : mpStatement(apStatement), mID(aID), mRow(apStatement, aID) {} - reference operator*() const + reference operator*() const noexcept { return mRow; } @@ -180,22 +186,22 @@ class StatementExecutor return &mRow; } - reference operator++() noexcept + RowIterator& operator++() noexcept { mRow = Row(mpStatement, ++mID); advance(); - return mRow; + return *this; } - value_type operator++(int) + /// Prefer to use prefix increment (++it) + RowIterator operator++(int) noexcept { - Row copy{ mRow }; - mRow = Row(mpStatement, ++mID); + RowIterator copy{ *this }; advance(); return copy; } - bool operator==(const RowIterator& aIt) const; - bool operator!=(const RowIterator& aIt) const + bool operator==(const RowIterator& aIt) const noexcept; + bool operator!=(const RowIterator& aIt) const noexcept { return !(*this == aIt); } @@ -204,7 +210,7 @@ class StatementExecutor /// Executing next statement step void advance() noexcept; - TRowWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object + TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object uint16_t mID{}; //!< Current row number /// Internal row object storage @@ -223,9 +229,12 @@ class StatementExecutor RowIterator begin(); /** + * @brief Empty RowIterator without any connection to exisiting statements. + * Use to find if RowIterator is out of steps. + * * @return RowIterator to non-exisitng step */ - RowIterator end(); + RowIterator end() noexcept; protected: /** @@ -239,32 +248,23 @@ class StatementExecutor explicit StatementExecutor(sqlite3* apSQLite, const std::string& aQuery); /** - * @brief Return a std::shared_ptr with SQLite Statement Object. + * @brief Return a std::shared_ptr with prepared SQLite Statement Object. * - * @return raw pointer to Statement Object + * @return TRawStatementPtr with SQLite Statement Object */ - StatementPtr::TStatementPtr getStatement() const noexcept + StatementPtr::TRawStatementPtr getStatementPtr() const noexcept { return mpStatement->mpStatement; } /** - * @brief Return a prepared SQLite Statement Object. + * @brief Return a pointer to prepared SQLite Statement Object. * - * Throw an exception if the statement object was not prepared. - * @return raw pointer to Prepared Statement Object + * @return Raw pointer to SQLite Statement Object */ - sqlite3_stmt* getPreparedStatement() const; - - /** - * @brief Return a prepared SQLite Statement Object. - * - * Throw an exception if the statement object was not prepared. - * @return raw pointer to Prepared Statement Object - */ - TRowWeakPtr getExecutorWeakPtr() const + sqlite3_stmt* getStatement() const noexcept { - return mpStatement; + return mpStatement->getStatement(); } //////////////////////////////////////////////////////////////////////////// @@ -272,7 +272,9 @@ class StatementExecutor /** * @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 + * @param[in] aRet SQLite return code to test against the SQLITE_OK expected value. + * + * @throws SQLite::Exception when aRet isn't SQLITE_OK. */ void check(const int aRet) const { @@ -284,6 +286,8 @@ class StatementExecutor /** * @brief Check if there is a row of result returned by executeStep(), else throw a SQLite::Exception. + * + * @throws SQLite::Exception when mbHasRow is false. */ void checkRow() const { @@ -295,6 +299,8 @@ class StatementExecutor /** * @brief Check if there is a Column index is in the range of columns in the result. + * + * @throws SQLite::Exception when aIndex is out of bounds. */ void checkIndex(const int aIndex) const { @@ -305,17 +311,12 @@ class StatementExecutor } private: - /// Get column number and create map with columns names + /// Get column number and create map with columns names. void createColumnInfo(); - // xD - bool checkReturnCode(int aReturnCode) const; - // xD - bool checkReturnCode(int aReturnCode, int aErrorCode) const; - /// Shared Pointer to this object. - /// Allows RowIterator to execute next step - TRowPtr mpStatement{}; + /// Allows RowIterator to execute next step. + TStatementPtr mpStatement{}; 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() diff --git a/include/SQLiteCpp/StatementPtr.h b/include/SQLiteCpp/StatementPtr.h index cfbeb6bd..fd6135c7 100644 --- a/include/SQLiteCpp/StatementPtr.h +++ b/include/SQLiteCpp/StatementPtr.h @@ -3,6 +3,7 @@ * @ingroup SQLiteCpp * @brief Pointer for prepared SQLite Statement Object * + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2022 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -39,11 +40,11 @@ struct StatementPtr StatementPtr(sqlite3* apSQLite, const std::string& aQuery); /// Shared pointer to SQLite prepared Statement Object - using TStatementPtr = std::shared_ptr; + using TRawStatementPtr = std::shared_ptr; - sqlite3* const mpConnection; //!< Pointer to SQLite Database Connection Handle - TStatementPtr const mpStatement; //!< Shared Pointer to the prepared SQLite Statement Object - std::size_t mCurrentStep = 0; //!< Current step of prepared Statement Object + sqlite3* const mpConnection; //!< Pointer to SQLite Database Connection Handle + TRawStatementPtr const mpStatement; //!< Shared Pointer to the prepared SQLite Statement Object + std::size_t mCurrentStep = 0; //!< Current step of prepared Statement Object /// Resets SQLite Statement Object int reset() noexcept; @@ -57,19 +58,19 @@ struct StatementPtr * * @return Pointer to SQLite Statement Object */ - sqlite3_stmt* getPreparedStatement() const; + sqlite3_stmt* getStatement() const noexcept; private: /// Create prepared SQLite Statement Object - TStatementPtr prepareStatement(sqlite3* apConnection, const std::string& aQuery) const; + TRawStatementPtr prepareStatement(sqlite3* apConnection, const std::string& aQuery) const; }; /// Shared pointer to SQLite StatementPtr -using TRowPtr = std::shared_ptr; +using TStatementPtr = const std::shared_ptr; /// Weak pointer to SQLite StatementPtr -using TRowWeakPtr = std::weak_ptr; +using TStatementWeakPtr = std::weak_ptr; } // namespace SQLite diff --git a/src/Column.cpp b/src/Column.cpp index 3d1201f6..2e90fb5e 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -26,7 +26,7 @@ const int Null = SQLITE_NULL; // Encapsulation of a Column in a row of the result pointed by the prepared Statement. -Column::Column(const StatementPtr::TStatementPtr& aStmtPtr, int aIndex) : +Column::Column(const StatementPtr::TRawStatementPtr& aStmtPtr, int aIndex) : mStmtPtr(aStmtPtr), mIndex(aIndex) { diff --git a/src/Row.cpp b/src/Row.cpp index 222d9c57..c023539f 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Container for SQLite Statement Object step * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2015-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -19,7 +19,7 @@ namespace SQLite { - Row::Row(TRowWeakPtr apStatement, std::size_t aID) : + Row::Row(TStatementWeakPtr apStatement, std::size_t aID) : mpStatement(apStatement), ID(aID) { } @@ -28,7 +28,7 @@ namespace SQLite { auto statement = mpStatement.lock(); - return (SQLITE_NULL == sqlite3_column_type(statement->getPreparedStatement(), aIndex)); + return (SQLITE_NULL == sqlite3_column_type(statement->getStatement(), aIndex)); } const char* Row::getText(uint32_t aColumnID) const noexcept @@ -36,7 +36,7 @@ namespace SQLite auto statement = mpStatement.lock(); - auto pText = reinterpret_cast(sqlite3_column_text(statement->getPreparedStatement(), aColumnID)); + auto pText = reinterpret_cast(sqlite3_column_text(statement->getStatement(), aColumnID)); return (pText ? pText : ""); } diff --git a/src/Statement.cpp b/src/Statement.cpp index b12618ff..03f0d2e5 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -28,48 +28,48 @@ Statement::Statement(const Database& aDatabase, const std::string& aQuery) : // Clears away all the bindings of a prepared statement (can be associated with #reset() above). void Statement::clearBindings() { - const int ret = sqlite3_clear_bindings(getPreparedStatement()); + const int ret = sqlite3_clear_bindings(getStatement()); check(ret); } // Get bind parameter index int Statement::getIndex(const char * const apName) const { - return sqlite3_bind_parameter_index(getPreparedStatement(), apName); + return sqlite3_bind_parameter_index(getStatement(), apName); } // Bind an 32bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const int32_t aValue) { - const int ret = sqlite3_bind_int(getPreparedStatement(), aIndex, aValue); + const int ret = sqlite3_bind_int(getStatement(), aIndex, aValue); check(ret); } // Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const uint32_t aValue) { - const int ret = sqlite3_bind_int64(getPreparedStatement(), aIndex, aValue); + const int ret = sqlite3_bind_int64(getStatement(), aIndex, aValue); check(ret); } // Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const int64_t aValue) { - const int ret = sqlite3_bind_int64(getPreparedStatement(), aIndex, aValue); + const int ret = sqlite3_bind_int64(getStatement(), aIndex, aValue); check(ret); } // Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const double aValue) { - const int ret = sqlite3_bind_double(getPreparedStatement(), aIndex, aValue); + const int ret = sqlite3_bind_double(getStatement(), aIndex, aValue); check(ret); } // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const std::string& aValue) { - const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.c_str(), + const int ret = sqlite3_bind_text(getStatement(), aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); check(ret); } @@ -77,21 +77,21 @@ void Statement::bind(const int aIndex, const std::string& aValue) // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const char* apValue) { - const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, apValue, -1, SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(getStatement(), aIndex, apValue, -1, SQLITE_TRANSIENT); check(ret); } // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const void* apValue, const int aSize) { - const int ret = sqlite3_bind_blob(getPreparedStatement(), aIndex, apValue, aSize, SQLITE_TRANSIENT); + const int ret = sqlite3_bind_blob(getStatement(), aIndex, apValue, aSize, SQLITE_TRANSIENT); check(ret); } // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const std::string& aValue) { - const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.c_str(), + const int ret = sqlite3_bind_text(getStatement(), aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_STATIC); check(ret); } @@ -99,21 +99,21 @@ void Statement::bindNoCopy(const int aIndex, const std::string& aValue) // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const char* apValue) { - const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, apValue, -1, SQLITE_STATIC); + const int ret = sqlite3_bind_text(getStatement(), aIndex, apValue, -1, SQLITE_STATIC); check(ret); } // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const void* apValue, const int aSize) { - const int ret = sqlite3_bind_blob(getPreparedStatement(), aIndex, apValue, aSize, SQLITE_STATIC); + const int ret = sqlite3_bind_blob(getStatement(), aIndex, apValue, aSize, SQLITE_STATIC); check(ret); } // Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex) { - const int ret = sqlite3_bind_null(getPreparedStatement(), aIndex); + const int ret = sqlite3_bind_null(getStatement(), aIndex); check(ret); } @@ -125,7 +125,7 @@ Column Statement::getColumn(const int aIndex) const checkIndex(aIndex); // Share the Statement Object handle with the new Column created - return Column(getStatement(), aIndex); + return Column(getStatementPtr(), aIndex); } // Return a copy of the column data specified by its column name starting at 0 @@ -136,7 +136,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(getStatement(), index); + return Column(getStatementPtr(), index); } // Test if the column is NULL @@ -144,21 +144,21 @@ bool Statement::isColumnNull(const int aIndex) const { checkRow(); checkIndex(aIndex); - return (SQLITE_NULL == sqlite3_column_type(getPreparedStatement(), aIndex)); + return (SQLITE_NULL == sqlite3_column_type(getStatement(), aIndex)); } bool Statement::isColumnNull(const char* apName) const { checkRow(); const int index = getColumnIndex(apName); - return (SQLITE_NULL == sqlite3_column_type(getPreparedStatement(), index)); + return (SQLITE_NULL == sqlite3_column_type(getStatement(), index)); } // Return the named assigned to the specified result column (potentially aliased) const char* Statement::getColumnName(const int aIndex) const { checkIndex(aIndex); - return sqlite3_column_name(getPreparedStatement(), aIndex); + return sqlite3_column_name(getStatement(), aIndex); } #ifdef SQLITE_ENABLE_COLUMN_METADATA @@ -166,7 +166,7 @@ const char* Statement::getColumnName(const int aIndex) const const char* Statement::getColumnOriginName(const int aIndex) const { checkIndex(aIndex); - return sqlite3_column_origin_name(getPreparedStatement(), aIndex); + return sqlite3_column_origin_name(getStatement(), aIndex); } #endif @@ -187,7 +187,7 @@ int Statement::getColumnIndex(const char* apName) const const char * Statement::getColumnDeclaredType(const int aIndex) const { checkIndex(aIndex); - const char * result = sqlite3_column_decltype(getPreparedStatement(), aIndex); + const char * result = sqlite3_column_decltype(getStatement(), aIndex); if (!result) { throw SQLite::Exception("Could not determine declared column type."); @@ -200,12 +200,12 @@ const char * Statement::getColumnDeclaredType(const int aIndex) const int Statement::getBindParameterCount() const noexcept { - return sqlite3_bind_parameter_count(getStatement().get()); + return sqlite3_bind_parameter_count(getStatement()); } // Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. std::string Statement::getExpandedSQL() const { - char* expanded = sqlite3_expanded_sql(getPreparedStatement()); + char* expanded = sqlite3_expanded_sql(getStatement()); std::string expandedString(expanded); sqlite3_free(expanded); return expandedString; diff --git a/src/StatementExecutor.cpp b/src/StatementExecutor.cpp index d9d20744..15b95ceb 100644 --- a/src/StatementExecutor.cpp +++ b/src/StatementExecutor.cpp @@ -3,6 +3,7 @@ * @ingroup SQLiteCpp * @brief Step executor for SQLite prepared Statement Object * + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2012-2021 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -26,7 +27,7 @@ namespace SQLite void StatementExecutor::createColumnInfo() { - mColumnCount = sqlite3_column_count(mpStatement->getPreparedStatement()); + mColumnCount = sqlite3_column_count(getStatement()); if (!mColumnNames.empty()) @@ -35,30 +36,12 @@ namespace SQLite // Build the map of column name and index for (int i = 0; i < mColumnCount; ++i) { - const char* pName = sqlite3_column_name(getPreparedStatement(), i); + const char* pName = sqlite3_column_name(getStatement(), i); mColumnNames.emplace(pName, i); } } - bool StatementExecutor::checkReturnCode(int aReturnCode) const - { - if (aReturnCode == getErrorCode()) - { - throw SQLite::Exception(mpStatement->mpConnection, aReturnCode); - } - return true; - } - - bool StatementExecutor::checkReturnCode(int aReturnCode, int aErrorCode) const - { - if (aReturnCode == aErrorCode) - { - throw SQLite::Exception(mpStatement->mpConnection, aReturnCode); - } - return true; - } - - // Reset the statement to make it ready for a new execution (see also #clearBindings() bellow) + // Reset the statement to make it ready for a new execution. This doesn't clear bindings. void StatementExecutor::reset() { const int ret = tryReset(); @@ -78,7 +61,11 @@ namespace SQLite const int ret = tryExecuteStep(); if ((SQLITE_ROW != ret) && (SQLITE_DONE != ret)) // on row or no (more) row ready, else it's a problem { - if (checkReturnCode(ret)) + if (ret == getErrorCode()) + { + throw SQLite::Exception(mpStatement->mpConnection, ret); + } + else { throw SQLite::Exception("Statement needs to be reseted", ret); } @@ -97,7 +84,11 @@ namespace SQLite { throw SQLite::Exception("exec() does not expect results. Use executeStep."); } - else if (checkReturnCode(ret)) + else if (ret == getErrorCode()) + { + throw SQLite::Exception(mpStatement->mpConnection, ret); + } + else { throw SQLite::Exception("Statement needs to be reseted", ret); } @@ -151,11 +142,7 @@ namespace SQLite return sqlite3_errmsg(mpStatement->mpConnection); } - // Return prepered SQLite statement object or throw - sqlite3_stmt* StatementExecutor::getPreparedStatement() const - { - return mpStatement->getPreparedStatement(); - } + //////////////////////////////////////////////////////////////////////////// StatementExecutor::RowIterator StatementExecutor::begin() { @@ -164,7 +151,7 @@ namespace SQLite return StatementExecutor::RowIterator(mpStatement, 0); } - StatementExecutor::RowIterator StatementExecutor::end() + StatementExecutor::RowIterator StatementExecutor::end() noexcept { return StatementExecutor::RowIterator(); } @@ -179,12 +166,12 @@ namespace SQLite if (SQLITE_ROW != ret) { - mpStatement = TRowWeakPtr{}; + mpStatement = TStatementWeakPtr{}; return; } } - bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const + bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const noexcept { auto left = mpStatement.lock(); auto right = aIt.mpStatement.lock(); @@ -198,4 +185,5 @@ namespace SQLite return mID == aIt.mID; } + } // namespace SQLite diff --git a/src/StatementPtr.cpp b/src/StatementPtr.cpp index 6f1edca6..6b88575d 100644 --- a/src/StatementPtr.cpp +++ b/src/StatementPtr.cpp @@ -3,6 +3,7 @@ * @ingroup SQLiteCpp * @brief Pointer for prepared SQLite Statement Object * + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) * Copyright (c) 2022 Sebastien Rombauts (sebastien.rombauts@gmail.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt @@ -22,7 +23,7 @@ namespace SQLite mpConnection(apSQLite), mpStatement(prepareStatement(apSQLite, aQuery)) {} - sqlite3_stmt* StatementPtr::getPreparedStatement() const + sqlite3_stmt* StatementPtr::getStatement() const noexcept { return mpStatement.get(); } @@ -39,7 +40,7 @@ namespace SQLite return sqlite3_step(mpStatement.get()); } - StatementPtr::TStatementPtr StatementPtr::prepareStatement(sqlite3* apConnection, const std::string& aQuery) const + StatementPtr::TRawStatementPtr StatementPtr::prepareStatement(sqlite3* apConnection, const std::string& aQuery) const { if (!apConnection) throw SQLite::Exception("Can't create statement without valid database connection"); @@ -53,7 +54,7 @@ namespace SQLite throw SQLite::Exception(apConnection, ret); } - return TStatementPtr(statement, [](sqlite3_stmt* stmt) + return TRawStatementPtr(statement, [](sqlite3_stmt* stmt) { sqlite3_finalize(stmt); }); From a81ee9eee2c677fd67f5bd07cb8c33d1073d7a95 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 1 Aug 2022 21:36:27 +0200 Subject: [PATCH 07/11] Change how Column handle statement pointer to enforce better safety. --- include/SQLiteCpp/Column.h | 43 ++++++++++++-------- include/SQLiteCpp/Row.h | 27 +++---------- include/SQLiteCpp/Statement.h | 5 +-- include/SQLiteCpp/StatementExecutor.h | 8 ++-- include/SQLiteCpp/StatementPtr.h | 2 +- src/Column.cpp | 58 +++++++++++++-------------- src/Row.cpp | 20 --------- src/Statement.cpp | 4 +- src/StatementPtr.cpp | 3 +- tests/Column_test.cpp | 10 ++--- tests/Statement_test.cpp | 1 - 11 files changed, 75 insertions(+), 106 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 61786a9b..ad914202 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -50,16 +50,16 @@ class Column * * @param[in] aStmtPtr Shared pointer to the prepared SQLite Statement Object. * @param[in] aIndex Index of the column in the row of result, starting at 0 - * - * @throws Exception is thrown in case of error, then the Column object is NOT constructed. */ - explicit Column(const StatementPtr::TRawStatementPtr& aStmtPtr, int aIndex); + explicit Column(const TStatementPtr& aStatementPtr, uint16_t aIndex) noexcept : + mStatementPtr(aStatementPtr), + mIndex(aIndex), mRowIndex(mStatementPtr->mCurrentStep) {} Column(const Column&) = delete; Column& operator=(const Column&) = delete; - Column(Column&& aColumn) = default; - Column& operator=(Column&& aColumn) = default; + Column(Column&& aColumn) noexcept = default; + Column& operator=(Column&& aColumn) noexcept = default; /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) @@ -80,27 +80,27 @@ class Column #endif /// Return the integer value of the column. - int32_t getInt() const noexcept; + int32_t getInt() const; /// Return the 32bits unsigned integer value of the column (note that SQLite3 does not support unsigned 64bits). - uint32_t getUInt() const noexcept; + uint32_t getUInt() const; /// Return the 64bits integer value of the column (note that SQLite3 does not support unsigned 64bits). - int64_t getInt64() const noexcept; + int64_t getInt64() const; /// Return the double (64bits float) value of the column - double getDouble() const noexcept; + double getDouble() const; /** * @brief Return a pointer to the text value (NULL terminated string) of the column. * * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), * thus you must copy it before using it beyond its scope (to a std::string for instance). */ - const char* getText(const char* apDefaultValue = "") const noexcept; + const char* getText(const char* apDefaultValue = "") const; /** * @brief Return a pointer to the binary blob value of the column. * * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), * thus you must copy it before using it beyond its scope (to a std::string for instance). */ - const void* getBlob() const noexcept; + const void* getBlob() const; /** * @brief Return a std::string for a TEXT or BLOB column. * @@ -153,12 +153,12 @@ class Column * - size in bytes of the binary blob returned by getBlob() * - 0 for a NULL value */ - int getBytes() const noexcept; + int getBytes() const; /// Alias returning the number of bytes used by the text (or blob) value of the column - int size() const noexcept + int size() const { - return getBytes (); + return getBytes(); } /// Inline cast operators to basic types @@ -230,8 +230,19 @@ class Column } private: - StatementPtr::TRawStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object - int mIndex; ///< Index of the column in the row of result, starting at 0 + /** + * @brief Returns pointer to SQLite Statement Object to use with sqlite3 API. + * Checks if SQLite::Column is used with proper statement step. + * + * @throws SQLite::Exception when statement has changed since this object constrution. + * + * @return Raw pointer to SQLite Statement Object + */ + sqlite3_stmt* getStatement() const; + + TStatementPtr mStatementPtr; ///< Shared Pointer to the prepared SQLite Statement Object + uint16_t mIndex; ///< Index of the column in the row of result, starting at 0 + std::size_t mRowIndex; ///< Index of the statement row, starting at 0 }; /** diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index fd3f783c..86032351 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include @@ -26,35 +27,17 @@ namespace SQLite class Row { public: - Row(TStatementWeakPtr apStatement, std::size_t aID); + Row(TStatementWeakPtr apStatement, std::size_t aID) : + mpStatement(apStatement), mID(aID) {} std::size_t getRowNumber() const { - return ID; + return mID; } - /** - * @brief Test if the column value is NULL - * - * @param[in] aIndex Index of the column, starting at 0 - * - * @return true if the column value is NULL - * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. - */ - bool isColumnNull(const int aIndex) const; - - /** - * @brief Return a pointer to the text value (NULL terminated string) of the column. - * - * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), - * thus you must copy it before using it beyond its scope (to a std::string for instance). - */ - const char* getText(uint32_t aColumnID) const noexcept; - private: TStatementWeakPtr mpStatement; - std::size_t ID; + std::size_t mID; }; } // namespace SQLite diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index cef8a677..ab351da8 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -25,7 +25,6 @@ namespace SQLite // Forward declaration class Database; -class Column; /** @@ -379,7 +378,7 @@ class Statement : public StatementExecutor * Thus, you should instead extract immediately its data (getInt(), getText()...) * and use or copy this data for any later usage. */ - Column getColumn(const int aIndex) const; + Column getColumn(const int aIndex); /** * @brief Return a copy of the column data specified by its column name (less efficient than using an index) @@ -410,7 +409,7 @@ class Statement : public StatementExecutor * * Throw an exception if the specified name is not one of the aliased name of the columns in the result. */ - Column getColumn(const char* apName) const; + Column getColumn(const char* apName); #if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) // c++14: Visual Studio 2015 /** diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h index dc7b1b99..3561b2a8 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -250,11 +250,11 @@ class StatementExecutor /** * @brief Return a std::shared_ptr with prepared SQLite Statement Object. * - * @return TRawStatementPtr with SQLite Statement Object + * @return TStatementPtr with SQLite Statement Object */ - StatementPtr::TRawStatementPtr getStatementPtr() const noexcept + TStatementPtr getStatementPtr() const noexcept { - return mpStatement->mpStatement; + return mpStatement; } /** @@ -316,7 +316,7 @@ class StatementExecutor /// Shared Pointer to this object. /// Allows RowIterator to execute next step. - TStatementPtr mpStatement{}; + const TStatementPtr mpStatement{}; 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() diff --git a/include/SQLiteCpp/StatementPtr.h b/include/SQLiteCpp/StatementPtr.h index fd6135c7..b7c9c2d1 100644 --- a/include/SQLiteCpp/StatementPtr.h +++ b/include/SQLiteCpp/StatementPtr.h @@ -67,7 +67,7 @@ struct StatementPtr /// Shared pointer to SQLite StatementPtr -using TStatementPtr = const std::shared_ptr; +using TStatementPtr = std::shared_ptr; /// Weak pointer to SQLite StatementPtr using TStatementWeakPtr = std::weak_ptr; diff --git a/src/Column.cpp b/src/Column.cpp index 2e90fb5e..e956d2fb 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -25,90 +25,90 @@ const int BLOB = SQLITE_BLOB; const int Null = SQLITE_NULL; -// Encapsulation of a Column in a row of the result pointed by the prepared Statement. -Column::Column(const StatementPtr::TRawStatementPtr& aStmtPtr, int aIndex) : - mStmtPtr(aStmtPtr), - mIndex(aIndex) -{ - if (!aStmtPtr) - { - throw SQLite::Exception("Statement was destroyed"); - } -} - // Return the named assigned to this result column (potentially aliased) const char* Column::getName() const noexcept { - return sqlite3_column_name(mStmtPtr.get(), mIndex); + return sqlite3_column_name(mStatementPtr->getStatement(), mIndex); } #ifdef SQLITE_ENABLE_COLUMN_METADATA // Return the name of the table column that is the origin of this result column const char* Column::getOriginName() const noexcept { - return sqlite3_column_origin_name(mStmtPtr.get(), mIndex); + return sqlite3_column_origin_name(mStatementPtr->getStatement(), mIndex); } #endif // Return the integer value of the column specified by its index starting at 0 -int32_t Column::getInt() const noexcept +int32_t Column::getInt() const { - return sqlite3_column_int(mStmtPtr.get(), mIndex); + return sqlite3_column_int(getStatement(), mIndex); } // Return the unsigned integer value of the column specified by its index starting at 0 -uint32_t Column::getUInt() const noexcept +uint32_t Column::getUInt() const { return static_cast(getInt64()); } // Return the 64bits integer value of the column specified by its index starting at 0 -int64_t Column::getInt64() const noexcept +int64_t Column::getInt64() const { - return sqlite3_column_int64(mStmtPtr.get(), mIndex); + return sqlite3_column_int64(getStatement(), mIndex); } // Return the double value of the column specified by its index starting at 0 -double Column::getDouble() const noexcept +double Column::getDouble() const { - return sqlite3_column_double(mStmtPtr.get(), mIndex); + return sqlite3_column_double(getStatement(), mIndex); } // Return a pointer to the text value (NULL terminated string) of the column specified by its index starting at 0 -const char* Column::getText(const char* apDefaultValue /* = "" */) const noexcept +const char* Column::getText(const char* apDefaultValue /* = "" */) const { - auto pText = reinterpret_cast(sqlite3_column_text(mStmtPtr.get(), mIndex)); + auto pText = reinterpret_cast(sqlite3_column_text(getStatement(), mIndex)); return (pText ? pText : apDefaultValue); } // Return a pointer to the blob value (*not* NULL terminated) of the column specified by its index starting at 0 -const void* Column::getBlob() const noexcept +const void* Column::getBlob() const { - return sqlite3_column_blob(mStmtPtr.get(), mIndex); + return sqlite3_column_blob(getStatement(), mIndex); } // Return a std::string to a TEXT or BLOB column std::string Column::getString() const { + auto statement = getStatement(); + // Note: using sqlite3_column_blob and not sqlite3_column_text // - no need for sqlite3_column_text to add a \0 on the end, as we're getting the bytes length directly - auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + auto data = static_cast(sqlite3_column_blob(statement, mIndex)); // SQLite docs: "The safest policy is to invokeā€¦ sqlite3_column_blob() followed by sqlite3_column_bytes()" // Note: std::string is ok to pass nullptr as first arg, if length is 0 - return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); + return std::string(data, sqlite3_column_bytes(statement, mIndex)); } // Return the type of the value of the column int Column::getType() const noexcept { - return sqlite3_column_type(mStmtPtr.get(), mIndex); + return sqlite3_column_type(mStatementPtr->getStatement(), mIndex); } // Return the number of bytes used by the text value of the column -int Column::getBytes() const noexcept +int Column::getBytes() const { - return sqlite3_column_bytes(mStmtPtr.get(), mIndex); + return sqlite3_column_bytes(getStatement(), mIndex); +} + +sqlite3_stmt* Column::getStatement() const +{ + if (mStatementPtr->mCurrentStep != mRowIndex) + { + throw SQLite::Exception("Column is used after SQLite Statement Object has been changed"); + } + return mStatementPtr->getStatement(); } // Standard std::ostream inserter diff --git a/src/Row.cpp b/src/Row.cpp index c023539f..44822c06 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -19,25 +19,5 @@ namespace SQLite { - Row::Row(TStatementWeakPtr apStatement, std::size_t aID) : - mpStatement(apStatement), ID(aID) - { - } - - bool Row::isColumnNull(const int aIndex) const - { - auto statement = mpStatement.lock(); - - return (SQLITE_NULL == sqlite3_column_type(statement->getStatement(), aIndex)); - } - - const char* Row::getText(uint32_t aColumnID) const noexcept - { - auto statement = mpStatement.lock(); - - - auto pText = reinterpret_cast(sqlite3_column_text(statement->getStatement(), aColumnID)); - return (pText ? pText : ""); - } } // namespace SQLite diff --git a/src/Statement.cpp b/src/Statement.cpp index 03f0d2e5..e14102c2 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -119,7 +119,7 @@ void Statement::bind(const int aIndex) // 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 +Column Statement::getColumn(const int aIndex) { checkRow(); checkIndex(aIndex); @@ -130,7 +130,7 @@ Column Statement::getColumn(const int aIndex) const // Return a copy of the column data specified by its column name starting at 0 // (use the Column copy-constructor) -Column Statement::getColumn(const char* apName) const +Column Statement::getColumn(const char* apName) { checkRow(); const int index = getColumnIndex(apName); diff --git a/src/StatementPtr.cpp b/src/StatementPtr.cpp index 6b88575d..2e2df7a0 100644 --- a/src/StatementPtr.cpp +++ b/src/StatementPtr.cpp @@ -40,7 +40,8 @@ namespace SQLite return sqlite3_step(mpStatement.get()); } - StatementPtr::TRawStatementPtr StatementPtr::prepareStatement(sqlite3* apConnection, const std::string& aQuery) const + StatementPtr::TRawStatementPtr StatementPtr::prepareStatement(sqlite3* apConnection, + const std::string& aQuery) const { if (!apConnection) throw SQLite::Exception("Can't create statement without valid database connection"); diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index fac607bc..3b2187cc 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -260,16 +260,12 @@ TEST(Column, shared_ptr) query->executeStep(); column0 = query->getColumn(0); EXPECT_EQ(true, column0.isInteger()); + query->executeStep(); // query is done - - // Undefined behavior - // auto x = column0.getInt(); + EXPECT_THROW(column0.getInt(), SQLite::Exception); query.reset(); - - // Undefined behavior - // auto x = column0.getInt(); - // bool isInt = column0.isInteger(); + EXPECT_THROW(column0.getInt(), SQLite::Exception); EXPECT_STREQ("id", column0.getName()); } diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 96e5ec1f..22f13488 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -133,7 +133,6 @@ TEST(Statement, moveConstructor) const auto const_query = std::move(moved); auto index = const_query.getColumnIndex("value"); EXPECT_EQ(1, index); - EXPECT_NO_THROW(const_query.getColumn(index)); // Moved statements should throw EXPECT_THROW(query.getColumnIndex("value"), SQLite::Exception); From 5dcde6dbe4d05f9cb565412f61e298732a8293e3 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Mon, 1 Aug 2022 23:54:07 +0200 Subject: [PATCH 08/11] Fix RowIterator and Add bidirectional ColumnIterator --- include/SQLiteCpp/Column.h | 4 +- include/SQLiteCpp/Row.h | 66 ++++++++++++++++++++++++++- include/SQLiteCpp/StatementExecutor.h | 16 +++---- src/Row.cpp | 17 +++++++ src/StatementExecutor.cpp | 9 ++-- 5 files changed, 96 insertions(+), 16 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index ad914202..8f98dfb5 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -55,8 +55,8 @@ class Column mStatementPtr(aStatementPtr), mIndex(aIndex), mRowIndex(mStatementPtr->mCurrentStep) {} - Column(const Column&) = delete; - Column& operator=(const Column&) = delete; + Column(const Column&) noexcept = default; + Column& operator=(const Column&) noexcept = default; Column(Column&& aColumn) noexcept = default; Column& operator=(Column&& aColumn) noexcept = default; diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 86032351..59cefee0 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -35,9 +35,73 @@ class Row return mID; } + /** + * @brief RandomAccessIterator for row columns. + */ + class ColumnIterator + { + public: + //TODO: using iterator_category = std::random_access_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; + using value_type = Column; + using reference = const Column&; + using pointer = const Column*; + using difference_type = std::ptrdiff_t; + + ColumnIterator() = default; + ColumnIterator(TStatementPtr apStatement, uint16_t aID) : + mpStatement(apStatement), mID(aID), mColumn(apStatement, aID) {} + + reference operator*() const noexcept + { + return mColumn; + } + pointer operator->() const noexcept + { + return &mColumn; + } + + ColumnIterator& operator++() noexcept + { + mColumn = Column(mpStatement, ++mID); + return *this; + } + ColumnIterator operator++(int) noexcept + { + ColumnIterator copy{ *this }; + mColumn = Column(mpStatement, ++mID); + return copy; + } + ColumnIterator& operator--() noexcept + { + mColumn = Column(mpStatement, --mID); + return *this; + } + ColumnIterator operator--(int) noexcept + { + ColumnIterator copy{ *this }; + mColumn = Column(mpStatement, --mID); + return copy; + } + + bool operator==(const ColumnIterator& aIt) const noexcept; + bool operator!=(const ColumnIterator& aIt) const noexcept + { + return !(*this == aIt); + } + + private: + TStatementPtr mpStatement{}; //!< Shared pointer to prepared Statement Object + std::size_t mRowID{}; //!< Current row number + uint16_t mID{}; //!< Current column number + + /// Internal column object storage + Column mColumn{ mpStatement, mID }; + }; + private: TStatementWeakPtr mpStatement; - std::size_t mID; + std::size_t mID; }; } // namespace SQLite diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h index 3561b2a8..2efea287 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -173,9 +173,8 @@ class StatementExecutor using pointer = const Row*; using difference_type = std::ptrdiff_t; - RowIterator() = default; - RowIterator(TStatementWeakPtr apStatement, uint16_t aID) : - mpStatement(apStatement), mID(aID), mRow(apStatement, aID) {} + explicit RowIterator(TStatementWeakPtr apStatement = TStatementWeakPtr{}) : + mpStatement(apStatement) {} reference operator*() const noexcept { @@ -188,16 +187,13 @@ class StatementExecutor RowIterator& operator++() noexcept { - mRow = Row(mpStatement, ++mID); advance(); return *this; } - /// Prefer to use prefix increment (++it) - RowIterator operator++(int) noexcept + RowIterator& operator++(int) noexcept { - RowIterator copy{ *this }; advance(); - return copy; + return *this; } bool operator==(const RowIterator& aIt) const noexcept; @@ -210,8 +206,8 @@ class StatementExecutor /// Executing next statement step void advance() noexcept; - TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object - uint16_t mID{}; //!< Current row number + TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object + std::size_t mID{}; //!< Current row number /// Internal row object storage Row mRow{ mpStatement, mID }; diff --git a/src/Row.cpp b/src/Row.cpp index 44822c06..55c7e08d 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -19,5 +19,22 @@ namespace SQLite { + bool Row::ColumnIterator::operator==(const ColumnIterator& aIt) const noexcept + { + const auto& left = mpStatement; + const auto& right = aIt.mpStatement; + + if (mID != aIt.mID) + return false; + + if (!left && !right) + return true; + + if (left != right) + return false; + + return mRowID == aIt.mRowID; + } + } // namespace SQLite diff --git a/src/StatementExecutor.cpp b/src/StatementExecutor.cpp index 15b95ceb..30215632 100644 --- a/src/StatementExecutor.cpp +++ b/src/StatementExecutor.cpp @@ -9,6 +9,7 @@ * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * or copy at http://opensource.org/licenses/MIT) */ +#include #include #include @@ -148,7 +149,7 @@ namespace SQLite { reset(); tryExecuteStep(); - return StatementExecutor::RowIterator(mpStatement, 0); + return StatementExecutor::RowIterator(mpStatement); } StatementExecutor::RowIterator StatementExecutor::end() noexcept @@ -158,6 +159,8 @@ namespace SQLite void StatementExecutor::RowIterator::advance() noexcept { + mRow = Row(mpStatement, ++mID); + if (mpStatement.expired()) return; @@ -173,8 +176,8 @@ namespace SQLite bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const noexcept { - auto left = mpStatement.lock(); - auto right = aIt.mpStatement.lock(); + const auto left = mpStatement.lock(); + const auto right = aIt.mpStatement.lock(); if (!left && !right) return true; From 95158f329ae30e198b562f6327fedfd3074ff64b Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Tue, 2 Aug 2022 01:58:09 +0200 Subject: [PATCH 09/11] Implement working Row class. Move more statement info to StatementPtr. Replace int&uint16_t with int_fast16_t. --- include/SQLiteCpp/Column.h | 12 +++- include/SQLiteCpp/Row.h | 82 +++++++++++++++++++++++---- include/SQLiteCpp/Statement.h | 54 +++++++++--------- include/SQLiteCpp/StatementExecutor.h | 37 ++++++------ include/SQLiteCpp/StatementPtr.h | 8 +++ src/Row.cpp | 54 +++++++++++++++++- src/Statement.cpp | 34 +++++------ src/StatementExecutor.cpp | 22 ------- src/StatementPtr.cpp | 10 +++- tests/Statement_test.cpp | 1 - 10 files changed, 210 insertions(+), 104 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 8f98dfb5..fa18da8e 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -51,7 +51,7 @@ class Column * @param[in] aStmtPtr Shared pointer to the prepared SQLite Statement Object. * @param[in] aIndex Index of the column in the row of result, starting at 0 */ - explicit Column(const TStatementPtr& aStatementPtr, uint16_t aIndex) noexcept : + explicit Column(const TStatementPtr& aStatementPtr, int_fast16_t aIndex) noexcept : mStatementPtr(aStatementPtr), mIndex(aIndex), mRowIndex(mStatementPtr->mCurrentStep) {} @@ -61,6 +61,14 @@ class Column Column(Column&& aColumn) noexcept = default; Column& operator=(Column&& aColumn) noexcept = default; + /** + * @return Column index in statement return table. + */ + int_fast16_t getIndex() const noexcept + { + return mIndex; + } + /** * @brief Return a pointer to the named assigned to this result column (potentially aliased) * @@ -241,7 +249,7 @@ class Column sqlite3_stmt* getStatement() const; TStatementPtr mStatementPtr; ///< Shared Pointer to the prepared SQLite Statement Object - uint16_t mIndex; ///< Index of the column in the row of result, starting at 0 + int_fast16_t mIndex; ///< Index of the column in the row of result, starting at 0 std::size_t mRowIndex; ///< Index of the statement row, starting at 0 }; diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 59cefee0..b3e8372b 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -11,9 +11,11 @@ */ #pragma once +#include #include #include +#include #include #include @@ -22,26 +24,58 @@ namespace SQLite { /** -* @brief CLASS IS WIP! +* @brief Small class returned by StatementExecutor::RowIterator. +* +* Use with for-range to iterate on statement columns. */ class Row { public: - Row(TStatementWeakPtr apStatement, std::size_t aID) : - mpStatement(apStatement), mID(aID) {} + Row(TStatementWeakPtr apStatement, std::size_t aID); - std::size_t getRowNumber() const + /** + * @return Row ID/steps executed since statement start (starting at 0). + */ + std::size_t getRowNumber() const noexcept { return mID; } + /** + * @return Column with given index + * + * @throws SQLite::Exception when index is out of bounds + */ + Column operator[](int_fast16_t aIndex) const; + /** + * @return Column with given name + * + * @throws SQLite::Exception when there is no column with given name + */ + Column operator[](const char* aName) const; + + //////////////////////////////////////////////////////////////////////////// + + /** + * @brief Return the index of the specified (potentially aliased) column name + * + * @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name) + * + * @throws SQLite::Exception if the specified name is not known. + */ + int_fast16_t getColumnIndex(const char* apName) const; + + //////////////////////////////////////////////////////////////////////////// + /** * @brief RandomAccessIterator for row columns. + * + * Use by using for-range on Row or by calling Row::begin(). */ class ColumnIterator { public: - //TODO: using iterator_category = std::random_access_iterator_tag; + // TODO: using iterator_category = std::random_access_iterator_tag; using iterator_category = std::bidirectional_iterator_tag; using value_type = Column; using reference = const Column&; @@ -49,8 +83,8 @@ class Row using difference_type = std::ptrdiff_t; ColumnIterator() = default; - ColumnIterator(TStatementPtr apStatement, uint16_t aID) : - mpStatement(apStatement), mID(aID), mColumn(apStatement, aID) {} + ColumnIterator(TStatementPtr apStatement, int_fast16_t aID) : + mpStatement(apStatement), mRowID(apStatement->mCurrentStep), mID(aID), mColumn(apStatement, aID) {} reference operator*() const noexcept { @@ -91,17 +125,41 @@ class Row } private: - TStatementPtr mpStatement{}; //!< Shared pointer to prepared Statement Object - std::size_t mRowID{}; //!< Current row number - uint16_t mID{}; //!< Current column number + TStatementPtr mpStatement; //!< Shared pointer to prepared Statement Object + std::size_t mRowID; //!< Current row number + int_fast16_t mID; //!< Current column number /// Internal column object storage Column mColumn{ mpStatement, mID }; }; + /** + * @return RowIterator to first column of this prepared statement. + */ + ColumnIterator begin() const; + + /** + * @return RowIterator to after the last column of this prepared statement. + */ + ColumnIterator end() const; + private: - TStatementWeakPtr mpStatement; - std::size_t mID; + /** + * @brief Checks if weak_ptr contains existing SQLite Statement Object. + * + * @throws SQLite::Exception when weak_ptr is expired. + */ + void checkStatement() const + { + if (mpStatement.expired()) + { + throw SQLite::Exception("Row is used after destruction of SQLite Statement Object"); + } + } + + TStatementWeakPtr mpStatement; //!< Weak Pointer to the prepared SQLite Statement Object + std::size_t mID; //!< Index of the statement row, starting at 0 + int_fast16_t mColumnCount{}; //!< Number of columns in row }; } // namespace SQLite diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index ab351da8..7a45886f 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -93,42 +93,42 @@ class Statement : public StatementExecutor // => if you know what you are doing, use bindNoCopy() instead of bind() SQLITECPP_PURE_FUNC - int getIndex(const char * const apName) const; + int getIndex(const char* const apName) const; /** * @brief Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const int aIndex, const int32_t aValue); + void bind(const int_fast16_t aIndex, const int32_t aValue); /** * @brief Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const int aIndex, const uint32_t aValue); + void bind(const int_fast16_t aIndex, const uint32_t aValue); /** * @brief Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const int aIndex, const int64_t aValue); + void bind(const int_fast16_t aIndex, const int64_t aValue); /** * @brief Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const int aIndex, const double aValue); + void bind(const int_fast16_t aIndex, const double aValue); /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const int aIndex, const std::string& aValue); + void bind(const int_fast16_t aIndex, const std::string& aValue); /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const int aIndex, const char* apValue); + void bind(const int_fast16_t aIndex, const char* apValue); /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const int aIndex, const void* apValue, const int aSize); + void bind(const int_fast16_t aIndex, const void* apValue, const int aSize); /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). * @@ -136,7 +136,7 @@ class Statement : public StatementExecutor * * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ - void bindNoCopy(const int aIndex, const std::string& aValue); + void bindNoCopy(const int_fast16_t aIndex, const std::string& aValue); /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -144,19 +144,19 @@ class Statement : public StatementExecutor * * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ - void bindNoCopy(const int aIndex, const char* apValue); + void bindNoCopy(const int_fast16_t aIndex, const char* apValue); /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ - void bindNoCopy(const int aIndex, const void* apValue, const int aSize); + void bindNoCopy(const int_fast16_t aIndex, const void* apValue, const int aSize); /** * @brief Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @see clearBindings() to set all bound parameters to NULL. */ - void bind(const int aIndex); + void bind(const int_fast16_t aIndex); /** * @brief Bind an int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) @@ -258,28 +258,28 @@ class Statement : public StatementExecutor /** * @brief Bind an int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const std::string& aName, const int32_t aValue) + void bind(const std::string& aName, const int32_t aValue) { bind(aName.c_str(), aValue); } /** * @brief Bind a 32bits unsigned int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const std::string& aName, const uint32_t aValue) + void bind(const std::string& aName, const uint32_t aValue) { bind(aName.c_str(), aValue); } /** * @brief Bind a 64bits int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const std::string& aName, const int64_t aValue) + void bind(const std::string& aName, const int64_t aValue) { bind(aName.c_str(), aValue); } /** * @brief Bind a double (64bits float) value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ - void bind(const std::string& aName, const double aValue) + void bind(const std::string& aName, const double aValue) { bind(aName.c_str(), aValue); } @@ -288,7 +288,7 @@ class Statement : public StatementExecutor * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const std::string& aName, const std::string& aValue) + void bind(const std::string& aName, const std::string& aValue) { bind(aName.c_str(), aValue); } @@ -297,7 +297,7 @@ class Statement : public StatementExecutor * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const std::string& aName, const char* apValue) + void bind(const std::string& aName, const char* apValue) { bind(aName.c_str(), apValue); } @@ -306,7 +306,7 @@ class Statement : public StatementExecutor * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const std::string& aName, const void* apValue, const int aSize) + void bind(const std::string& aName, const void* apValue, const int aSize) { bind(aName.c_str(), apValue, aSize); } @@ -378,7 +378,7 @@ class Statement : public StatementExecutor * Thus, you should instead extract immediately its data (getInt(), getText()...) * and use or copy this data for any later usage. */ - Column getColumn(const int aIndex); + Column getColumn(const int_fast16_t aIndex); /** * @brief Return a copy of the column data specified by its column name (less efficient than using an index) @@ -431,12 +431,12 @@ class Statement : public StatementExecutor * * @note Requires std=C++14 */ - template + template T getColumns() { checkRow(); checkIndex(N - 1); - return getColumns(std::make_integer_sequence{}); + return getColumns(std::make_integer_sequence{}); } private: @@ -449,7 +449,7 @@ class Statement : public StatementExecutor * @return Column object for each column in statement */ template - T getColumns(const std::integer_sequence) + T getColumns(const std::integer_sequence) { return T{ Column(getStatementPtr(), Is)... }; } @@ -466,7 +466,7 @@ class Statement : public StatementExecutor * * Throw an exception if the specified index is out of the [0, getColumnCount()) range. */ - bool isColumnNull(const int aIndex) const; + bool isColumnNull(const int_fast16_t aIndex) const; /** * @brief Test if the column value is NULL @@ -488,7 +488,7 @@ class Statement : public StatementExecutor * * Throw an exception if the specified index is out of the [0, getColumnCount()) range. */ - const char* getColumnName(const int aIndex) const; + const char* getColumnName(const int_fast16_t aIndex) const; #ifdef SQLITE_ENABLE_COLUMN_METADATA /** @@ -500,7 +500,7 @@ class Statement : public StatementExecutor * * Throw an exception if the specified index is out of the [0, getColumnCount()) range. */ - const char* getColumnOriginName(const int aIndex) const; + const char* getColumnOriginName(const int_fast16_t aIndex) const; #endif /** @@ -528,7 +528,7 @@ class Statement : public StatementExecutor * - the statement is not a SELECT query * - the column at aIndex is not a table column but an expression or subquery */ - const char* getColumnDeclaredType(const int aIndex) const; + const char* getColumnDeclaredType(const int_fast16_t aIndex) const; //////////////////////////////////////////////////////////////////////////// diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h index 2efea287..114c050a 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -18,7 +18,6 @@ #include #include #include -#include namespace SQLite @@ -29,6 +28,8 @@ extern const int OK; ///< SQLITE_OK /** * @brief Base class for prepared SQLite Statement. * +* Use with for-range to iterate on statement rows. +* * You should use SQLite::Statement or (if you had a reson) * inherit this class to create your own Statement executor class. * Either way you should look at SQLite::Statement documentation @@ -44,9 +45,6 @@ extern const int OK; ///< SQLITE_OK class StatementExecutor { public: - /// Type to store columns names and indexes - using TColumnsMap = std::map>; - StatementExecutor(const StatementExecutor&) = delete; StatementExecutor& operator=(const StatementExecutor&) = delete; @@ -126,13 +124,13 @@ class StatementExecutor /// Return the number of columns in the result set returned by the prepared statement int getColumnCount() const noexcept { - return mColumnCount; + return mpStatement->mColumnCount; } /// Get columns names with theirs ids - const TColumnsMap& getColumnsNames() const noexcept + const StatementPtr::TColumnsMap& getColumnsNames() const noexcept { - return mColumnNames; + return mpStatement->mColumnNames; } /// true when a row has been fetched with executeStep() @@ -163,6 +161,9 @@ class StatementExecutor * @brief InputIterator for statement steps. * * Remember that this iterator is changing state of StatementExecutor. + * + * Use this iterator by using for-range on StatementExecutor + * or by calling StatementExecutor::begin(). */ class RowIterator { @@ -206,8 +207,8 @@ class StatementExecutor /// Executing next statement step void advance() noexcept; - TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object - std::size_t mID{}; //!< Current row number + TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object + std::size_t mID{}; //!< Current row number /// Internal row object storage Row mRow{ mpStatement, mID }; @@ -241,7 +242,8 @@ class StatementExecutor * * @throws Exception is thrown in case of error, then the StatementExecutor object is NOT constructed. */ - explicit StatementExecutor(sqlite3* apSQLite, const std::string& aQuery); + explicit StatementExecutor(sqlite3* apSQLite, const std::string& aQuery) : + mpStatement(std::make_shared(apSQLite, aQuery)) {} /** * @brief Return a std::shared_ptr with prepared SQLite Statement Object. @@ -298,28 +300,21 @@ class StatementExecutor * * @throws SQLite::Exception when aIndex is out of bounds. */ - void checkIndex(const int aIndex) const + void checkIndex(const int_fast16_t aIndex) const { - if ((aIndex < 0) || (aIndex >= mColumnCount)) + if ((aIndex < 0) || (mpStatement->mColumnCount <= aIndex)) { throw SQLite::Exception("Column index out of range."); } } private: - /// Get column number and create map with columns names. - void createColumnInfo(); - /// Shared Pointer to this object. /// Allows RowIterator to execute next step. const TStatementPtr mpStatement{}; - 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{}; + 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 }; diff --git a/include/SQLiteCpp/StatementPtr.h b/include/SQLiteCpp/StatementPtr.h index b7c9c2d1..07113bc9 100644 --- a/include/SQLiteCpp/StatementPtr.h +++ b/include/SQLiteCpp/StatementPtr.h @@ -13,6 +13,7 @@ #include #include +#include // Forward declaration to avoid inclusion of in a header struct sqlite3; @@ -42,9 +43,16 @@ struct StatementPtr /// Shared pointer to SQLite prepared Statement Object using TRawStatementPtr = std::shared_ptr; + /// Type to store columns names and indexes + using TColumnsMap = std::map>; + sqlite3* const mpConnection; //!< Pointer to SQLite Database Connection Handle TRawStatementPtr const mpStatement; //!< Shared Pointer to the prepared SQLite Statement Object std::size_t mCurrentStep = 0; //!< Current step of prepared Statement Object + int_fast16_t mColumnCount; //!< Number of columns in the result of the prepared statement + + /// Map of columns index by name (mutable so getColumnIndex can be const) + mutable TColumnsMap mColumnNames{}; /// Resets SQLite Statement Object int reset() noexcept; diff --git a/src/Row.cpp b/src/Row.cpp index 55c7e08d..911a03a7 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -17,7 +17,60 @@ namespace SQLite { + Row::Row(TStatementWeakPtr apStatement, std::size_t aID) : + mpStatement(apStatement), mID(aID) + { + checkStatement(); + auto statement = mpStatement.lock(); + mColumnCount = statement->mColumnCount; + } + + Column Row::operator[](int_fast16_t aIndex) const + { + checkStatement(); + if (mColumnCount <= aIndex) + { + throw SQLite::Exception("Column index out of range."); + } + return Column(mpStatement.lock(), aIndex); + } + + Column Row::operator[](const char* aName) const + { + return Column(mpStatement.lock(), getColumnIndex(aName)); + } + + int_fast16_t Row::getColumnIndex(const char* apName) const + { + checkStatement(); + auto statement = mpStatement.lock(); + + const auto& columns = statement->mColumnNames; + const auto iIndex = columns.find(apName); + if (iIndex == columns.end()) + { + throw SQLite::Exception("Unknown column name."); + } + + return iIndex->second; + } + + //////////////////////////////////////////////////////////////////////////// + + Row::ColumnIterator Row::begin() const + { + checkStatement(); + auto statement = mpStatement.lock(); + return ColumnIterator(statement, 0); + } + + Row::ColumnIterator Row::end() const + { + checkStatement(); + auto statement = mpStatement.lock(); + return ColumnIterator(statement, statement->mColumnCount); + } bool Row::ColumnIterator::operator==(const ColumnIterator& aIt) const noexcept { @@ -36,5 +89,4 @@ namespace SQLite return mRowID == aIt.mRowID; } - } // namespace SQLite diff --git a/src/Statement.cpp b/src/Statement.cpp index e14102c2..e9c0fa08 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -33,41 +33,41 @@ void Statement::clearBindings() } // Get bind parameter index -int Statement::getIndex(const char * const apName) const +int Statement::getIndex(const char* const apName) const { return sqlite3_bind_parameter_index(getStatement(), apName); } // Bind an 32bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const int32_t aValue) +void Statement::bind(const int_fast16_t aIndex, const int32_t aValue) { const int ret = sqlite3_bind_int(getStatement(), aIndex, aValue); check(ret); } // Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const uint32_t aValue) +void Statement::bind(const int_fast16_t aIndex, const uint32_t aValue) { const int ret = sqlite3_bind_int64(getStatement(), aIndex, aValue); check(ret); } // Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const int64_t aValue) +void Statement::bind(const int_fast16_t aIndex, const int64_t aValue) { const int ret = sqlite3_bind_int64(getStatement(), aIndex, aValue); check(ret); } // Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const double aValue) +void Statement::bind(const int_fast16_t aIndex, const double aValue) { const int ret = sqlite3_bind_double(getStatement(), aIndex, aValue); check(ret); } // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const std::string& aValue) +void Statement::bind(const int_fast16_t aIndex, const std::string& aValue) { const int ret = sqlite3_bind_text(getStatement(), aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); @@ -75,21 +75,21 @@ void Statement::bind(const int aIndex, const std::string& aValue) } // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const char* apValue) +void Statement::bind(const int_fast16_t aIndex, const char* apValue) { const int ret = sqlite3_bind_text(getStatement(), aIndex, apValue, -1, SQLITE_TRANSIENT); check(ret); } // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const void* apValue, const int aSize) +void Statement::bind(const int_fast16_t aIndex, const void* apValue, const int aSize) { const int ret = sqlite3_bind_blob(getStatement(), aIndex, apValue, aSize, SQLITE_TRANSIENT); check(ret); } // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bindNoCopy(const int aIndex, const std::string& aValue) +void Statement::bindNoCopy(const int_fast16_t aIndex, const std::string& aValue) { const int ret = sqlite3_bind_text(getStatement(), aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_STATIC); @@ -97,21 +97,21 @@ void Statement::bindNoCopy(const int aIndex, const std::string& aValue) } // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bindNoCopy(const int aIndex, const char* apValue) +void Statement::bindNoCopy(const int_fast16_t aIndex, const char* apValue) { const int ret = sqlite3_bind_text(getStatement(), aIndex, apValue, -1, SQLITE_STATIC); check(ret); } // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bindNoCopy(const int aIndex, const void* apValue, const int aSize) +void Statement::bindNoCopy(const int_fast16_t aIndex, const void* apValue, const int aSize) { const int ret = sqlite3_bind_blob(getStatement(), aIndex, apValue, aSize, SQLITE_STATIC); check(ret); } // Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex) +void Statement::bind(const int_fast16_t aIndex) { const int ret = sqlite3_bind_null(getStatement(), aIndex); check(ret); @@ -119,7 +119,7 @@ void Statement::bind(const int aIndex) // 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) +Column Statement::getColumn(const int_fast16_t aIndex) { checkRow(); checkIndex(aIndex); @@ -140,7 +140,7 @@ Column Statement::getColumn(const char* apName) } // Test if the column is NULL -bool Statement::isColumnNull(const int aIndex) const +bool Statement::isColumnNull(const int_fast16_t aIndex) const { checkRow(); checkIndex(aIndex); @@ -155,7 +155,7 @@ bool Statement::isColumnNull(const char* apName) const } // Return the named assigned to the specified result column (potentially aliased) -const char* Statement::getColumnName(const int aIndex) const +const char* Statement::getColumnName(const int_fast16_t aIndex) const { checkIndex(aIndex); return sqlite3_column_name(getStatement(), aIndex); @@ -163,7 +163,7 @@ const char* Statement::getColumnName(const int aIndex) const #ifdef SQLITE_ENABLE_COLUMN_METADATA // Return the named assigned to the specified result column (potentially aliased) -const char* Statement::getColumnOriginName(const int aIndex) const +const char* Statement::getColumnOriginName(const int_fast16_t aIndex) const { checkIndex(aIndex); return sqlite3_column_origin_name(getStatement(), aIndex); @@ -184,7 +184,7 @@ int Statement::getColumnIndex(const char* apName) const return iIndex->second; } -const char * Statement::getColumnDeclaredType(const int aIndex) const +const char * Statement::getColumnDeclaredType(const int_fast16_t aIndex) const { checkIndex(aIndex); const char * result = sqlite3_column_decltype(getStatement(), aIndex); diff --git a/src/StatementExecutor.cpp b/src/StatementExecutor.cpp index 30215632..5b7f7d13 100644 --- a/src/StatementExecutor.cpp +++ b/src/StatementExecutor.cpp @@ -20,28 +20,6 @@ namespace SQLite { - StatementExecutor::StatementExecutor(sqlite3* apSQLite, const std::string& aQuery) : - mpStatement(std::make_shared(apSQLite, aQuery)) - { - createColumnInfo(); - } - - void StatementExecutor::createColumnInfo() - { - mColumnCount = sqlite3_column_count(getStatement()); - - - 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(getStatement(), i); - mColumnNames.emplace(pName, i); - } - } - // Reset the statement to make it ready for a new execution. This doesn't clear bindings. void StatementExecutor::reset() { diff --git a/src/StatementPtr.cpp b/src/StatementPtr.cpp index 2e2df7a0..0131fcac 100644 --- a/src/StatementPtr.cpp +++ b/src/StatementPtr.cpp @@ -21,7 +21,15 @@ namespace SQLite StatementPtr::StatementPtr(sqlite3* apSQLite, const std::string& aQuery) : mpConnection(apSQLite), mpStatement(prepareStatement(apSQLite, aQuery)) - {} + { + mColumnCount = sqlite3_column_count(getStatement()); + + // Build the map of column name and index + for (int i = 0; i < mColumnCount; ++i) + { + mColumnNames.emplace(sqlite3_column_name(getStatement(), i), i); + } + } sqlite3_stmt* StatementPtr::getStatement() const noexcept { diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 22f13488..b475def4 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -135,7 +135,6 @@ TEST(Statement, moveConstructor) EXPECT_EQ(1, index); // Moved statements should throw - EXPECT_THROW(query.getColumnIndex("value"), SQLite::Exception); EXPECT_THROW(query.getColumn(index), SQLite::Exception); } From 969949908a227ec567acf208caba5cb303a386f4 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Tue, 2 Aug 2022 02:33:31 +0200 Subject: [PATCH 10/11] Add some aliases to Row for getting column --- include/SQLiteCpp/Row.h | 52 +++++++++++++++++++++++++++++++++++++++-- src/Row.cpp | 4 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index b3e8372b..1f168790 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -41,18 +41,66 @@ class Row return mID; } + //////////////////////////////////////////////////////////////////////////// + + /** + * @return Column with given index + * + * @throws SQLite::Exception when index is out of bounds + */ + Column at(int_fast16_t aIndex) const; + /** + * @return Column with given name + * + * @throws SQLite::Exception when there is no column with given name + */ + Column at(const char* aName) const; + + /** + * @return Column with given index + * + * @note Alias of Column::at() + * + * @throws SQLite::Exception when index is out of bounds + */ + Column getColumn(int_fast16_t aIndex) const + { + return at(aIndex); + } + /** + * @return Column with given name + * + * @note Alias of Column::at() + * + * @throws SQLite::Exception when there is no column with given name + */ + Column getColumn(const char* aName) const + { + return at(aName); + } + /** * @return Column with given index * + * @note Alias of Column::at() + * * @throws SQLite::Exception when index is out of bounds */ - Column operator[](int_fast16_t aIndex) const; + Column operator[](int_fast16_t aIndex) const + { + return at(aIndex); + } /** * @return Column with given name * + * @note Alias of Column::at() + * * @throws SQLite::Exception when there is no column with given name */ - Column operator[](const char* aName) const; + Column operator[](const char* aName) const + { + return at(aName); + } //////////////////////////////////////////////////////////////////////////// diff --git a/src/Row.cpp b/src/Row.cpp index 911a03a7..56116e4c 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -25,7 +25,7 @@ namespace SQLite mColumnCount = statement->mColumnCount; } - Column Row::operator[](int_fast16_t aIndex) const + Column Row::at(int_fast16_t aIndex) const { checkStatement(); if (mColumnCount <= aIndex) @@ -35,7 +35,7 @@ namespace SQLite return Column(mpStatement.lock(), aIndex); } - Column Row::operator[](const char* aName) const + Column Row::at(const char* aName) const { return Column(mpStatement.lock(), getColumnIndex(aName)); } From 56899b602ec83b9f6e5c50ba652d0a74738c0016 Mon Sep 17 00:00:00 2001 From: Kacperos155 Date: Tue, 2 Aug 2022 03:18:01 +0200 Subject: [PATCH 11/11] Add Iterators tests --- CMakeLists.txt | 1 + include/SQLiteCpp/Row.h | 4 +- src/Row.cpp | 4 +- tests/Iterators.cpp | 128 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 tests/Iterators.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 23004e29..b6cabd9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,7 @@ set(SQLITECPP_TESTS tests/VariadicBind_test.cpp tests/Exception_test.cpp tests/ExecuteMany_test.cpp + tests/Iterators.cpp ) source_group(tests FILES ${SQLITECPP_TESTS}) diff --git a/include/SQLiteCpp/Row.h b/include/SQLiteCpp/Row.h index 1f168790..e5f633c9 100644 --- a/include/SQLiteCpp/Row.h +++ b/include/SQLiteCpp/Row.h @@ -55,7 +55,7 @@ class Row * @throws SQLite::Exception when there is no column with given name */ Column at(const char* aName) const; - + /** * @return Column with given index * @@ -78,7 +78,7 @@ class Row { return at(aName); } - + /** * @return Column with given index * diff --git a/src/Row.cpp b/src/Row.cpp index 56116e4c..71c65ca5 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -20,9 +20,9 @@ namespace SQLite Row::Row(TStatementWeakPtr apStatement, std::size_t aID) : mpStatement(apStatement), mID(aID) { - checkStatement(); auto statement = mpStatement.lock(); - mColumnCount = statement->mColumnCount; + if (statement) + mColumnCount = statement->mColumnCount; } Column Row::at(int_fast16_t aIndex) const diff --git a/tests/Iterators.cpp b/tests/Iterators.cpp new file mode 100644 index 00000000..5d65f645 --- /dev/null +++ b/tests/Iterators.cpp @@ -0,0 +1,128 @@ +/** + * @file Iterators.cpp + * @ingroup tests + * @brief Test of Statement iterators + * + * Copyright (c) 2022 Kacper Zielinski (KacperZ155@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include +#include + +#include + +#include +#include +#include + +SQLite::Database createDatabase() +{ + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + EXPECT_EQ(SQLite::OK, db.exec("CREATE TABLE test (number INTEGER, number_str TEXT)")); + + SQLite::Statement inserter(db, "INSERT INTO test VALUES(?,?)"); + for (int i = 10; i > 0; --i) + { + inserter.bind(1, i); + inserter.bind(2, std::to_string(i)); + EXPECT_EQ(1, inserter.exec()); + EXPECT_NO_THROW(inserter.reset()); + } + return db; +} + +TEST(Iterators, RowIterator) +{ + auto db = createDatabase(); + + SQLite::Statement query(db, "SELECT * FROM test"); + std::vector numbers; + + for (const auto& row : query) + { + numbers.push_back(row[0]); + } + + //TODO: EXPECT_TRUE(query.isDone()); + + const auto v_size = static_cast(numbers.size()); + for (int i = v_size; i > 0; --i) + { + EXPECT_EQ(i, numbers[v_size - i]); + } + + auto it = query.begin(); + ++it; + EXPECT_STREQ("number_str", it->getColumn(1).getName()); + EXPECT_EQ(1, it->getColumnIndex("number_str")); + + // getColumn aliases + EXPECT_EQ(9, it->at(0).getInt()); + EXPECT_EQ(9, it->getColumn(0).getInt()); + EXPECT_EQ(9, it->operator[](0).getInt()); + + auto it2 = query.begin(); + ++it2; + EXPECT_EQ(it, it2); + + // RowInterator is advancing common statement object + ++it; + EXPECT_EQ(it->at(0).getInt(), it2->at(0).getInt()); + // But iterators internal state is diffrent. + EXPECT_NE(it, it2); +} + +TEST(Iterators, RowIterator_STL_Algorithms) +{ + auto db = createDatabase(); + + SQLite::Statement query(db, "SELECT * FROM test"); + + for (auto it = query.begin(); it != query.end(); std::advance(it, 3)) + { + EXPECT_TRUE(it->getRowNumber() % 3 == 0); + } + + EXPECT_TRUE(std::all_of(query.begin(), query.end(), [](const SQLite::Row& row) + { + return row[0].getInt() > 0; + })); +} + +TEST(Iterators, ColumnIterator) +{ + auto db = createDatabase(); + + SQLite::Statement query_only1(db, "SELECT * FROM test LIMIT 1"); + std::vector numbers_str; + + for(const auto& row : query_only1) + for (const auto& column : row) + { + numbers_str.emplace_back(column.getText()); + } + + for (const auto& column : numbers_str) + { + EXPECT_EQ("10", column); + } +} + +TEST(Iterators, ColumnIterator_STL_Algorithms) +{ + auto db = createDatabase(); + + SQLite::Statement query_only1(db, "SELECT * FROM test LIMIT 1"); + std::vector numbers; + + for (const auto& row : query_only1) + { + EXPECT_EQ(2, std::count_if(row.begin(), row.end(), [i = 10](const SQLite::Column& column) + { + return column.getText() == std::to_string(i); + })); + } +}