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..b6cabd9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,8 +105,11 @@ 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/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}) @@ -119,8 +122,11 @@ 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/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 @@ -138,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/Column.h b/include/SQLiteCpp/Column.h index d9e7c344..fa18da8e 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 { @@ -53,11 +51,23 @@ 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); + explicit Column(const TStatementPtr& aStatementPtr, int_fast16_t aIndex) noexcept : + mStatementPtr(aStatementPtr), + mIndex(aIndex), mRowIndex(mStatementPtr->mCurrentStep) {} - // 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. + Column(const Column&) noexcept = default; + Column& operator=(const Column&) noexcept = default; + + 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) @@ -78,27 +88,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. * @@ -151,12 +161,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 @@ -228,8 +238,19 @@ class Column } private: - Statement::TStatementPtr 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 + 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 }; /** @@ -244,24 +265,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(mpPreparedStatement, 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 new file mode 100644 index 00000000..e5f633c9 --- /dev/null +++ b/include/SQLiteCpp/Row.h @@ -0,0 +1,213 @@ +/** + * @file Row.h + * @ingroup SQLiteCpp + * @brief Container for SQLite Statement Object step + * + * 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 + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + +#include +#include +#include + +#include +#include +#include + + +namespace SQLite +{ + +/** +* @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); + + /** + * @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 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 + { + 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 + { + return at(aName); + } + + //////////////////////////////////////////////////////////////////////////// + + /** + * @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; + 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, int_fast16_t aID) : + mpStatement(apStatement), mRowID(apStatement->mCurrentStep), 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 + 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: + /** + * @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 704d7deb..7a45886f 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) * @@ -10,6 +10,7 @@ */ #pragma once +#include #include #include // SQLITECPP_PURE_FUNC @@ -17,10 +18,6 @@ #include #include -// Forward declarations to avoid inclusion of in a header -struct sqlite3; -struct sqlite3_stmt; - namespace SQLite { @@ -28,9 +25,7 @@ namespace SQLite // Forward declaration class Database; -class Column; -extern const int OK; ///< SQLITE_OK /** * @brief RAII encapsulation of a prepared SQLite Statement. @@ -49,53 +44,29 @@ 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 StatementExecutor { 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 * * @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. - */ - Statement(const Database& aDatabase, const std::string& aQuery) : - Statement(aDatabase, aQuery.c_str()) - {} - - /** - * @brief Move an SQLite statement. - * - * @param[in] aStatement Statement to move + * @throws Exception is thrown in case of error, then the Statement object is NOT constructed. */ - Statement(Statement&& aStatement) noexcept; - Statement& operator=(Statement&& aStatement) noexcept = default; + Statement(const Database& aDatabase, const std::string& aQuery); - // 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; - /// 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. * @@ -122,42 +93,42 @@ class Statement // => 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). * @@ -165,7 +136,7 @@ class Statement * * @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) * @@ -173,19 +144,19 @@ class Statement * * @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) @@ -287,28 +258,28 @@ class Statement /** * @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); } @@ -317,7 +288,7 @@ class Statement * * @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); } @@ -326,7 +297,7 @@ class Statement * * @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); } @@ -335,7 +306,7 @@ class Statement * * @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); } @@ -382,62 +353,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 * @@ -463,7 +378,7 @@ class Statement * 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_fast16_t aIndex); /** * @brief Return a copy of the column data specified by its column name (less efficient than using an index) @@ -494,7 +409,7 @@ class Statement * * 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 /** @@ -516,16 +431,28 @@ class Statement * * @note Requires std=C++14 */ - template - T getColumns(); + template + 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(getStatementPtr(), Is)... }; + } public: #endif @@ -539,7 +466,7 @@ class Statement * * 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 @@ -561,7 +488,7 @@ class Statement * * 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 /** @@ -573,7 +500,7 @@ class Statement * * 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 /** @@ -601,12 +528,7 @@ class Statement * - 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; - - - /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). - int getChanges() const noexcept; - + const char* getColumnDeclaredType(const int_fast16_t aIndex) const; //////////////////////////////////////////////////////////////////////////// @@ -616,98 +538,14 @@ class Statement 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 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; + std::string mQuery; //!< UTF-8 SQL Query, }; diff --git a/include/SQLiteCpp/StatementExecutor.h b/include/SQLiteCpp/StatementExecutor.h new file mode 100644 index 00000000..114c050a --- /dev/null +++ b/include/SQLiteCpp/StatementExecutor.h @@ -0,0 +1,321 @@ +/** + * @file StatementExecutor.h + * @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 + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + +#include +#include +#include + +#include +#include +#include + + +namespace SQLite +{ + +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 +* +* 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++, +* 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 StatementExecutor +{ +public: + StatementExecutor(const StatementExecutor&) = delete; + StatementExecutor& operator=(const StatementExecutor&) = delete; + + StatementExecutor(StatementExecutor&&) = default; + StatementExecutor& operator=(StatementExecutor&&) = default; + + /** + * @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. + 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(); + + //////////////////////////////////////////////////////////////////////////// + + /// 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 noexcept + { + return mpStatement->mColumnCount; + } + + /// Get columns names with theirs ids + const StatementPtr::TColumnsMap& getColumnsNames() const noexcept + { + return mpStatement->mColumnNames; + } + + /// true when a row has been fetched with executeStep() + bool hasRow() const noexcept + { + return mbHasRow; + } + /// true when the last executeStep() had no more row to fetch + bool isDone() const noexcept + { + 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; + + //////////////////////////////////////////////////////////////////////////// + + /** + * @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 + { + 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; + + explicit RowIterator(TStatementWeakPtr apStatement = TStatementWeakPtr{}) : + mpStatement(apStatement) {} + + reference operator*() const noexcept + { + return mRow; + } + pointer operator->() const noexcept + { + return &mRow; + } + + RowIterator& operator++() noexcept + { + advance(); + return *this; + } + RowIterator& operator++(int) noexcept + { + advance(); + return *this; + } + + bool operator==(const RowIterator& aIt) const noexcept; + bool operator!=(const RowIterator& aIt) const noexcept + { + return !(*this == aIt); + } + + private: + /// Executing next statement step + void advance() noexcept; + + TStatementWeakPtr mpStatement{}; //!< Weak pointer to prepared Statement Object + std::size_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(); + + /** + * @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() noexcept; + +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 StatementExecutor object is NOT constructed. + */ + 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. + * + * @return TStatementPtr with SQLite Statement Object + */ + TStatementPtr getStatementPtr() const noexcept + { + return mpStatement; + } + + /** + * @brief Return a pointer to prepared SQLite Statement Object. + * + * @return Raw pointer to SQLite Statement Object + */ + sqlite3_stmt* getStatement() const noexcept + { + return mpStatement->getStatement(); + } + + //////////////////////////////////////////////////////////////////////////// + + /** + * @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. + * + * @throws SQLite::Exception when aRet isn't SQLITE_OK. + */ + void check(const int aRet) const + { + if (SQLite::OK != aRet) + { + throw SQLite::Exception(mpStatement->mpConnection, aRet); + } + } + + /** + * @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 + { + 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. + * + * @throws SQLite::Exception when aIndex is out of bounds. + */ + void checkIndex(const int_fast16_t aIndex) const + { + if ((aIndex < 0) || (mpStatement->mColumnCount <= aIndex)) + { + throw SQLite::Exception("Column index out of range."); + } + } + +private: + /// Shared Pointer to this object. + /// Allows RowIterator to execute next step. + const TStatementPtr mpStatement{}; + + 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 +}; + + +} // namespace SQLite diff --git a/include/SQLiteCpp/StatementPtr.h b/include/SQLiteCpp/StatementPtr.h new file mode 100644 index 00000000..07113bc9 --- /dev/null +++ b/include/SQLiteCpp/StatementPtr.h @@ -0,0 +1,84 @@ +/** + * @file StatementPtr.h + * @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 + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + +#include +#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 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; + + /// 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* getStatement() const noexcept; + +private: + /// Create prepared SQLite Statement Object + TRawStatementPtr prepareStatement(sqlite3* apConnection, const std::string& aQuery) const; +}; + + +/// Shared pointer to SQLite StatementPtr +using TStatementPtr = std::shared_ptr; + +/// Weak pointer to SQLite StatementPtr +using TStatementWeakPtr = std::weak_ptr; + + +} // namespace SQLite diff --git a/src/Column.cpp b/src/Column.cpp index 108f7f98..e956d2fb 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -10,9 +10,9 @@ */ #include -#include +#include -#include +#include namespace SQLite @@ -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 Statement::TStatementPtr& 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)); - return (pText?pText:apDefaultValue); + 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(getStatement(), mIndex); +} + +sqlite3_stmt* Column::getStatement() const { - return sqlite3_column_bytes(mStmtPtr.get(), mIndex); + 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 new file mode 100644 index 00000000..71c65ca5 --- /dev/null +++ b/src/Row.cpp @@ -0,0 +1,92 @@ +/** + * @file Row.cpp + * @ingroup SQLiteCpp + * @brief Container for SQLite Statement Object step + * + * 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 + * or copy at http://opensource.org/licenses/MIT) + */ +#include + +#include + +#include + +namespace SQLite +{ + Row::Row(TStatementWeakPtr apStatement, std::size_t aID) : + mpStatement(apStatement), mID(aID) + { + auto statement = mpStatement.lock(); + if (statement) + mColumnCount = statement->mColumnCount; + } + + Column Row::at(int_fast16_t aIndex) const + { + checkStatement(); + if (mColumnCount <= aIndex) + { + throw SQLite::Exception("Column index out of range."); + } + return Column(mpStatement.lock(), aIndex); + } + + Column Row::at(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 + { + 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/Statement.cpp b/src/Statement.cpp index 64e16d2d..e9c0fa08 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) * @@ -20,267 +20,163 @@ 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) : + StatementExecutor(aDatabase.getHandle(), aQuery), mQuery(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); } -int Statement::getIndex(const char * const apName) const +// 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) +void Statement::bind(const int_fast16_t 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) +void Statement::bind(const int_fast16_t 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) +void Statement::bind(const int_fast16_t 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) +void Statement::bind(const int_fast16_t 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) +void Statement::bind(const int_fast16_t 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); } // 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(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) +void Statement::bind(const int_fast16_t 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) +void Statement::bindNoCopy(const int_fast16_t 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); } // 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(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) +void Statement::bindNoCopy(const int_fast16_t 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) +void Statement::bind(const int_fast16_t aIndex) { - const int ret = sqlite3_bind_null(getPreparedStatement(), aIndex); + const int ret = sqlite3_bind_null(getStatement(), 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 +Column Statement::getColumn(const int_fast16_t aIndex) { checkRow(); checkIndex(aIndex); // Share the Statement Object handle with the new Column created - return Column(mpPreparedStatement, aIndex); + return Column(getStatementPtr(), aIndex); } // 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); // Share the Statement Object handle with the new Column created - return Column(mpPreparedStatement, index); + return Column(getStatementPtr(), index); } // 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); - 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 +const char* Statement::getColumnName(const int_fast16_t aIndex) const { checkIndex(aIndex); - return sqlite3_column_name(getPreparedStatement(), aIndex); + return sqlite3_column_name(getStatement(), aIndex); } #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(getPreparedStatement(), aIndex); + return sqlite3_column_origin_name(getStatement(), aIndex); } #endif // 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."); } @@ -288,10 +184,10 @@ 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(getPreparedStatement(), aIndex); + const char * result = sqlite3_column_decltype(getStatement(), aIndex); if (!result) { throw SQLite::Exception("Could not determine declared column type."); @@ -302,67 +198,18 @@ 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()); } // 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; } -// 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 diff --git a/src/StatementExecutor.cpp b/src/StatementExecutor.cpp new file mode 100644 index 00000000..5b7f7d13 --- /dev/null +++ b/src/StatementExecutor.cpp @@ -0,0 +1,170 @@ +/** + * @file StatementExecutor.cpp + * @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 + * or copy at http://opensource.org/licenses/MIT) + */ +#include +#include + +#include + +#include + +namespace SQLite +{ + + + // Reset the statement to make it ready for a new execution. This doesn't clear bindings. + void StatementExecutor::reset() + { + const int ret = tryReset(); + check(ret); + } + + int StatementExecutor::tryReset() noexcept + { + mbHasRow = false; + mbDone = false; + return mpStatement->reset(); + } + + // Execute a step of the query to fetch one row of results + 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 + { + if (ret == getErrorCode()) + { + throw SQLite::Exception(mpStatement->mpConnection, 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 StatementExecutor::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 == getErrorCode()) + { + throw SQLite::Exception(mpStatement->mpConnection, 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 getChanges(); + } + + int StatementExecutor::tryExecuteStep() noexcept + { + if (mbDone) + { + return SQLITE_MISUSE; // Statement needs to be reseted ! + } + + const auto ret = mpStatement->step(); + 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; + } + + // Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). + int StatementExecutor::getChanges() const noexcept + { + 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(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(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(mpStatement->mpConnection); + } + + //////////////////////////////////////////////////////////////////////////// + + StatementExecutor::RowIterator StatementExecutor::begin() + { + reset(); + tryExecuteStep(); + return StatementExecutor::RowIterator(mpStatement); + } + + StatementExecutor::RowIterator StatementExecutor::end() noexcept + { + return StatementExecutor::RowIterator(); + } + + void StatementExecutor::RowIterator::advance() noexcept + { + mRow = Row(mpStatement, ++mID); + + if (mpStatement.expired()) + return; + + auto statement = mpStatement.lock(); + auto ret = statement->step(); + + if (SQLITE_ROW != ret) + { + mpStatement = TStatementWeakPtr{}; + return; + } + } + + bool StatementExecutor::RowIterator::operator==(const RowIterator& aIt) const noexcept + { + const auto left = mpStatement.lock(); + const auto right = aIt.mpStatement.lock(); + + if (!left && !right) + return true; + + if (left != right) + return false; + + return mID == aIt.mID; + } + + +} // namespace SQLite diff --git a/src/StatementPtr.cpp b/src/StatementPtr.cpp new file mode 100644 index 00000000..0131fcac --- /dev/null +++ b/src/StatementPtr.cpp @@ -0,0 +1,73 @@ +/** + * @file StatementPtr.cpp + * @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 + * 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)) + { + 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 + { + 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::TRawStatementPtr 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 TRawStatementPtr(statement, [](sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + }); + } + + +} // namespace SQLite 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/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); + })); + } +} diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 96e5ec1f..b475def4 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -133,10 +133,8 @@ 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); EXPECT_THROW(query.getColumn(index), SQLite::Exception); }