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..8bf3292d 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -59,18 +59,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 +435,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..60c98cce 100644 --- a/include/SQLiteCpp/StatementExecutor.h +++ b/include/SQLiteCpp/StatementExecutor.h @@ -3,14 +3,14 @@ * @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 @@ -18,8 +18,6 @@ #include #include -// Forward declaration to avoid inclusion of in a header -struct sqlite3_stmt; namespace SQLite { @@ -44,24 +42,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 +167,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 +203,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 +242,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 +263,7 @@ class StatementExecutor */ TRowWeakPtr getExecutorWeakPtr() const { - return mpRowExecutor; + return mpStatement; } //////////////////////////////////////////////////////////////////////////// @@ -291,7 +277,7 @@ class StatementExecutor { if (SQLite::OK != aRet) { - throw SQLite::Exception(mpSQLite, aRet); + throw SQLite::Exception(mpStatement->mpConnection, aRet); } } @@ -318,18 +304,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..327cc9c1 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) - { - 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() + void 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..29e9b6ff --- /dev/null +++ b/src/StatementPtr.cpp @@ -0,0 +1,63 @@ +/** + * @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) + */ +#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) {