From ca7c0e5f1a84f14b5c0407fd5a7f23b92d2dd0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 25 Jul 2021 09:36:17 +0200 Subject: [PATCH 1/3] Added Database and Statement method getChanges() Fix #331 How to get the number of updated/deleted rows? Fix cpplint warnings about line size with a NOLINT comment when better to keep oneline --- include/SQLiteCpp/Database.h | 9 ++++++--- include/SQLiteCpp/Statement.h | 7 +++++-- src/Database.cpp | 8 +++++++- src/Statement.cpp | 8 +++++++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 1490fd8..5268353 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -14,7 +14,7 @@ // c++17: MinGW GCC version > 8 // c++17: Visual Studio 2017 version 15.7 -#if ((__cplusplus >= 201703L) && ((!defined(__MINGW32__) && !defined(__MINGW64__)) || (__GNUC__ > 8))) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) +#if ((__cplusplus >= 201703L) && ((!defined(__MINGW32__) && !defined(__MINGW64__)) || (__GNUC__ > 8))) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) // NOLINT #include #endif // c++17 @@ -169,7 +169,7 @@ public: // c++17: MinGW GCC version > 8 // c++17: Visual Studio 2017 version 15.7 - #if ((__cplusplus >= 201703L) && ((!defined(__MINGW32__) && !defined(__MINGW64__)) || (__GNUC__ > 8))) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) + #if ((__cplusplus >= 201703L) && ((!defined(__MINGW32__) && !defined(__MINGW64__)) || (__GNUC__ > 8))) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)) // NOLINT /** * @brief Open the provided database std::filesystem::path. @@ -241,7 +241,7 @@ public: void setBusyTimeout(const int aBusyTimeoutMs); /** - * @brief Shortcut to execute one or multiple statements without results. + * @brief Shortcut to execute one or multiple statements without results. Return the number of changes. * * This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" @@ -404,6 +404,9 @@ public: */ long long getLastInsertRowid() const noexcept; + /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). + int getChanges() const noexcept; + /// Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection (not DROP table). int getTotalChanges() const noexcept; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index a407417..84b0046 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -471,7 +471,7 @@ public: int tryExecuteStep() noexcept; /** - * @brief Execute a one-step query with no expected result. + * @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" @@ -488,7 +488,7 @@ public: * * @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 ! + * @throw SQLite::Exception in case of error, or if row of results are returned while they are not expected! */ int exec(); @@ -660,6 +660,9 @@ public: 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; + //////////////////////////////////////////////////////////////////////////// diff --git a/src/Database.cpp b/src/Database.cpp index 7b81f8b..db7dc63 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -112,7 +112,7 @@ void Database::setBusyTimeout(const int aBusyTimeoutMs) check(ret); } -// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). +// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). Return the number of changes. int Database::exec(const char* apQueries) { const int ret = tryExec(apQueries); @@ -155,6 +155,12 @@ long long Database::getLastInsertRowid() const noexcept return sqlite3_last_insert_rowid(getHandle()); } +// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). +int Database::getChanges() const noexcept +{ + return sqlite3_changes(getHandle()); +} + // Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection. int Database::getTotalChanges() const noexcept { diff --git a/src/Statement.cpp b/src/Statement.cpp index 8ab6862..f49a6eb 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -167,7 +167,7 @@ bool Statement::executeStep() return mbHasRow; // true only if one row is accessible by getColumn(N) } -// Execute a one-step query with no expected result +// Execute a one-step query with no expected result, and return the number of changes. int Statement::exec() { const int ret = tryExecuteStep(); @@ -310,6 +310,12 @@ 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(mStmtPtr); +} + int Statement::getBindParameterCount() const noexcept { return sqlite3_bind_parameter_count(mStmtPtr); From 74c8627df156b065d4a0daf7ef9e2434391d6471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 25 Jul 2021 09:39:09 +0200 Subject: [PATCH 2/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b523def..4a8609d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -196,3 +196,4 @@ Version 3.x - 2021 - #314 Add Database constructor for filesystem::path (#296) from ptrks - #295 Compile internal SQLite library with -ffunction-sections from smichaku - #299 Added Savepoint support from catalogm +- #333 Added Database and Statement getChanges() From 64c34bc7bdc01d101dc317b835bda0bb9d8603f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 25 Jul 2021 10:05:37 +0200 Subject: [PATCH 3/3] Added unit tests for new getChanges() and fix comment being too long --- examples/example1/main.cpp | 5 ++++- include/SQLiteCpp/Database.h | 2 +- include/SQLiteCpp/Statement.h | 2 +- src/Database.cpp | 5 +++-- src/Statement.cpp | 2 +- tests/Column_test.cpp | 4 +++- tests/Database_test.cpp | 20 ++++++++++++++++++-- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index d39b702..19d7fb7 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -4,7 +4,7 @@ * * Demonstrates how-to use the SQLite++ wrapper * - * Copyright (c) 2012-2020 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) @@ -308,6 +308,9 @@ int main () nb = db.exec("UPDATE test SET value=\"second-updated\" WHERE id='2'"); std::cout << "UPDATE test SET value=\"second-updated\" WHERE id='2', returned " << nb << std::endl; + nb = db.getTotalChanges(); + std::cout << "Nb of total changes since connection: " << nb << std::endl; + // Check the results : expect two row of result SQLite::Statement query(db, "SELECT * FROM test"); std::cout << "SELECT * FROM test :\n"; diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 5268353..cc15811 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Management of a SQLite Database Connection. * - * Copyright (c) 2012-2020 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) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 84b0046..15d5318 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result. * - * Copyright (c) 2012-2020 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) diff --git a/src/Database.cpp b/src/Database.cpp index db7dc63..eb76c88 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Management of a SQLite Database Connection. * - * Copyright (c) 2012-2020 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) @@ -112,7 +112,8 @@ void Database::setBusyTimeout(const int aBusyTimeoutMs) check(ret); } -// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). Return the number of changes. +// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). +// Return the number of changes. int Database::exec(const char* apQueries) { const int ret = tryExec(apQueries); diff --git a/src/Statement.cpp b/src/Statement.cpp index f49a6eb..0b9b3e5 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result. * - * Copyright (c) 2012-2020 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) diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index 9318f63..bf31c23 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -3,7 +3,7 @@ * @ingroup tests * @brief Test of a SQLiteCpp Column. * - * Copyright (c) 2012-2020 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) @@ -44,6 +44,7 @@ TEST(Column, basis) // Execute the one-step query to insert the row EXPECT_EQ(1, insert.exec()); EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(1, db.getTotalChanges()); EXPECT_THROW(insert.exec(), SQLite::Exception); // exec() shall throw as it needs to be reseted @@ -230,6 +231,7 @@ TEST(Column, stream) insert.bind(1, str); // Execute the one-step query to insert the row EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(1, db.getTotalChanges()); SQLite::Statement query(db, "SELECT * FROM test"); diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 2493a0d..22012f4 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -3,7 +3,7 @@ * @ingroup tests * @brief Test of a SQLiteCpp Database. * - * Copyright (c) 2012-2020 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) @@ -211,32 +211,38 @@ TEST(Database, exec) // NOTE: here exec() returns 0 only because it is the first statements since database connexion, // but its return is an undefined value for "CREATE TABLE" statements. db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_EQ(0, db.getChanges()); EXPECT_EQ(0, db.getLastInsertRowid()); EXPECT_EQ(0, db.getTotalChanges()); // first row : insert the "first" text value into new row of id 1 EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(1, db.getLastInsertRowid()); EXPECT_EQ(1, db.getTotalChanges()); // second row : insert the "second" text value into new row of id 2 EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\")")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(2, db.getTotalChanges()); // third row : insert the "third" text value into new row of id 3 const std::string insert("INSERT INTO test VALUES (NULL, \"third\")"); EXPECT_EQ(1, db.exec(insert)); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); EXPECT_EQ(3, db.getTotalChanges()); // update the second row : update text value to "second_updated" EXPECT_EQ(1, db.exec("UPDATE test SET value=\"second-updated\" WHERE id='2'")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 EXPECT_EQ(4, db.getTotalChanges()); // delete the third row EXPECT_EQ(1, db.exec("DELETE FROM test WHERE id='3'")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); EXPECT_EQ(5, db.getTotalChanges()); @@ -253,12 +259,14 @@ TEST(Database, exec) // insert two rows with two *different* statements => returns only 1, ie. for the second INSERT statement EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\");INSERT INTO test VALUES (NULL, \"second\");")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(7, db.getTotalChanges()); #if (SQLITE_VERSION_NUMBER >= 3007011) // insert two rows with only one statement (starting with SQLite 3.7.11) => returns 2 EXPECT_EQ(2, db.exec("INSERT INTO test VALUES (NULL, \"third\"), (NULL, \"fourth\");")); + EXPECT_EQ(2, db.getChanges()); EXPECT_EQ(4, db.getLastInsertRowid()); EXPECT_EQ(9, db.getTotalChanges()); #endif @@ -271,32 +279,38 @@ TEST(Database, tryExec) // Create a new table with an explicit "id" column aliasing the underlying rowid EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(0, db.getChanges()); EXPECT_EQ(0, db.getLastInsertRowid()); EXPECT_EQ(0, db.getTotalChanges()); // first row : insert the "first" text value into new row of id 1 EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, \"first\")")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(1, db.getLastInsertRowid()); EXPECT_EQ(1, db.getTotalChanges()); // second row : insert the "second" text value into new row of id 2 EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, \"second\")")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(2, db.getTotalChanges()); // third row : insert the "third" text value into new row of id 3 const std::string insert("INSERT INTO test VALUES (NULL, \"third\")"); EXPECT_EQ(SQLite::OK, db.tryExec(insert)); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); EXPECT_EQ(3, db.getTotalChanges()); // update the second row : update text value to "second_updated" EXPECT_EQ(SQLite::OK, db.tryExec("UPDATE test SET value=\"second-updated\" WHERE id='2'")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); // last inserted row ID is still 3 EXPECT_EQ(4, db.getTotalChanges()); // delete the third row EXPECT_EQ(SQLite::OK, db.tryExec("DELETE FROM test WHERE id='3'")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(3, db.getLastInsertRowid()); EXPECT_EQ(5, db.getTotalChanges()); @@ -309,14 +323,16 @@ TEST(Database, tryExec) EXPECT_EQ(SQLite::OK, db.tryExec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); EXPECT_EQ(5, db.getTotalChanges()); - // insert two rows with two *different* statements => returns only 1, ie. for the second INSERT statement + // insert two rows with two *different* statements => only 1 change, ie. for the second INSERT statement EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, \"first\");INSERT INTO test VALUES (NULL, \"second\");")); + EXPECT_EQ(1, db.getChanges()); EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(7, db.getTotalChanges()); #if (SQLITE_VERSION_NUMBER >= 3007011) // insert two rows with only one statement (starting with SQLite 3.7.11) EXPECT_EQ(SQLite::OK, db.tryExec("INSERT INTO test VALUES (NULL, \"third\"), (NULL, \"fourth\");")); + EXPECT_EQ(2, db.getChanges()); EXPECT_EQ(4, db.getLastInsertRowid()); EXPECT_EQ(9, db.getTotalChanges()); #endif