From 134efd639244d94ffd112f97e80e6d93651ddb5f Mon Sep 17 00:00:00 2001 From: "Jack.Yuan" Date: Fri, 17 Apr 2015 15:58:36 +0800 Subject: [PATCH 01/33] Update Statement.cpp --- src/Statement.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Statement.cpp b/src/Statement.cpp index d606e4d..ac18de4 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -256,6 +256,33 @@ Column Statement::getColumn(const int aIndex) return Column(mStmtPtr, 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* aName) +{ + int aIndex = -1; + + if (false == mbOk) + { + throw SQLite::Exception("No row to get a column from"); + } + else + { + for (int i = 0; i < mColumnCount; i++) { + if (sqlite3_column_name(mStmtPtr, i) == aName) + break; + } + + if ((aIndex < 0) || (aIndex >= mColumnCount)) { + throw SQLite::Exception("Column index out of range"); + } + + } + + // Share the Statement Object handle with the new Column created + return Column(mStmtPtr, aIndex); +} + // Test if the column is NULL bool Statement::isColumnNull(const int aIndex) const { From b6fdf50669eb0af70981c0dc3253a59856115f8a Mon Sep 17 00:00:00 2001 From: "Jack.Yuan" Date: Fri, 17 Apr 2015 15:59:58 +0800 Subject: [PATCH 02/33] Fix issue: Column by name #23 add method `Column getColumn(const char* aName);` in Statement.h --- include/SQLiteCpp/Statement.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 521d38f..7dab620 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -295,6 +295,33 @@ public: */ Column getColumn(const int aIndex); + /** + * @brief Return a copy of the column data specified by its column name + * + * Can be used to access the data of the current row of result when applicable, + * while the executeStep() method returns true. + * + * Throw an exception if there is no row to return a Column from : + * - before any executeStep() call + * - after the last executeStep() returned false + * - after a reset() call + * + * Throw an exception if the specified index is out of the [0, getColumnCount()) range. + * + * @param[in] aName Name of the column, starting at index 0 + * + * @note This method is no more const, starting in v0.5, + * which reflects the fact that the returned Column object will + * share the ownership of the underlying sqlite3_stmt. + * + * @warning The resulting Column object must not be memorized "as-is". + * Is is only a wrapper around the current result row, so it is only valid + * while the row from the Statement remains valid, that is only until next executeStep() call. + * Thus, you should instead extract immediately its data (getInt(), getText()...) + * and use or copy this data for any later usage. + */ + Column getColumn(const char* aName); + /** * @brief Test if the column value is NULL * From 170fdd55c7ed605bc2b8fcbae68c93a7a1ef8e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Mon, 20 Apr 2015 20:51:09 +0200 Subject: [PATCH 03/33] cpplint: do not break build for style warnings --- cpplint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpplint.py b/cpplint.py index e718083..6f4e751 100755 --- a/cpplint.py +++ b/cpplint.py @@ -4784,7 +4784,8 @@ def main(): ProcessFile(filename, _cpplint_state.verbose_level) _cpplint_state.PrintErrorCounts() - sys.exit(_cpplint_state.error_count > 0) + # SRombauts: do not break build for cpplint style warnings + #sys.exit(_cpplint_state.error_count > 0) if __name__ == '__main__': From 6499f93b07fb9ec803a59730a460b72d957c4738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Mon, 20 Apr 2015 21:04:18 +0200 Subject: [PATCH 04/33] Fix cmake find Python Interpreter (instead of Libs) - and use it --- CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e907d84..226e8dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,14 +157,15 @@ endif (WIN32) option(SQLITECPP_RUN_CPPLINT "Run cpplint.py tool for Google C++ StyleGuide." ON) if (SQLITECPP_RUN_CPPLINT) - find_package(PythonLibs) - if (PYTHONLIBS_FOUND) + find_package(PythonInterp) + if (PYTHONINTERP_FOUND) # add a cpplint target to the "all" target add_custom_target(SQLiteCpp_cpplint ALL - COMMAND python ${PROJECT_SOURCE_DIR}/cpplint.py ${CPPLINT_ARG_OUTPUT} ${CPPLINT_ARG_VERBOSE} ${CPPLINT_ARG_LINELENGTH} ${SQLITECPP_SRC} ${SQLITECPP_INC} + COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/cpplint.py ${CPPLINT_ARG_OUTPUT} ${CPPLINT_ARG_VERBOSE} ${CPPLINT_ARG_LINELENGTH} ${SQLITECPP_SRC} ${SQLITECPP_INC} ) - endif (PYTHONLIBS_FOUND) + message(STATUS "PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}") + endif (PYTHONINTERP_FOUND) else (SQLITECPP_RUN_CPPLINT) message(STATUS "SQLITECPP_RUN_CPPLINT OFF") endif (SQLITECPP_RUN_CPPLINT) From 29a964531430445733d81d2953a9902353003ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Mon, 20 Apr 2015 21:04:43 +0200 Subject: [PATCH 05/33] Fix cpplint style warning introduced by #45 --- src/Statement.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Statement.cpp b/src/Statement.cpp index ac18de4..4b5a73e 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -261,24 +261,23 @@ Column Statement::getColumn(const int aIndex) Column Statement::getColumn(const char* aName) { int aIndex = -1; - + if (false == mbOk) { throw SQLite::Exception("No row to get a column from"); } else { - for (int i = 0; i < mColumnCount; i++) { + for (int i = 0; i < mColumnCount; ++i) { if (sqlite3_column_name(mStmtPtr, i) == aName) break; } - + if ((aIndex < 0) || (aIndex >= mColumnCount)) { throw SQLite::Exception("Column index out of range"); } - } - + // Share the Statement Object handle with the new Column created return Column(mStmtPtr, aIndex); } From 82364ea419354f17660e4c05c097b48c8b88e70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Tue, 28 Apr 2015 20:08:38 +0200 Subject: [PATCH 06/33] Minor improvement of example1 to demonstrate variable reuse --- examples/example1/main.cpp | 73 +++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index c475319..b243664 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -2,7 +2,7 @@ * @file main.cpp * @brief A few short examples in a row. * - * Demonstrate how-to use the SQLite++ wrapper + * Demonstrates how-to use the SQLite++ wrapper * * Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) * @@ -91,7 +91,7 @@ int main () std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n"; // Test if the 'test' table exists - bool bExists = db.tableExists("test"); + const bool bExists = db.tableExists("test"); std::cout << "SQLite table 'test' exists=" << bExists << "\n"; // Get a single value result with an easy to use shortcut @@ -105,36 +105,38 @@ int main () query.bind(1, 2); std::cout << "binded with integer value '2' :\n"; + // Execute the first step of the query, to get the fist row of results, and name of columns + if (query.executeStep()) + { + // Show how to get the aliased names of the result columns. + const std::string name0 = query.getColumn(0).getName(); + const std::string name1 = query.getColumn(1).getName(); + const std::string name2 = query.getColumn(2).getName(); + std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; +#ifdef SQLITE_ENABLE_COLUMN_METADATA + // Show how to get origin names of the table columns from which theses result columns come from. + // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be + // also defined at compile times of the SQLite library itself. + const std::string oname0 = query.getColumn(0).getOriginName(); + const std::string oname1 = query.getColumn(1).getOriginName(); + const std::string oname2 = query.getColumn(2).getOriginName(); + std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n"; +#endif + // Demonstrates how to get some typed column value (and the equivalent explicit call) + const int id = query.getColumn(0); // = query.getColumn(0).getInt() + //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText() + const std::string value2 = query.getColumn(1); // = query.getColumn(1).getText() + const int bytes = query.getColumn(1).getBytes(); + const double weight = query.getColumn(2); // = query.getColumn(2).getInt() + + std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n"; + } + // Loop to execute the query step by step, to get one a row of results at a time while (query.executeStep()) { - // Demonstrate how to get some typed column value (and the equivalent explicit call) - int id = query.getColumn(0); // = query.getColumn(0).getInt() - //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText() - std::string value2 = query.getColumn(1); // = query.getColumn(1).getText() - int bytes = query.getColumn(1).getBytes(); - double weight = query.getColumn(2); // = query.getColumn(2).getInt() - - static bool bFirst = true; - if (bFirst) - { - // Show how to get the aliased names of the result columns. - std::string name0 = query.getColumn(0).getName(); - std::string name1 = query.getColumn(1).getName(); - std::string name2 = query.getColumn(2).getName(); - std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; -#ifdef SQLITE_ENABLE_COLUMN_METADATA - // Show how to get origin names of the table columns from which theses result columns come from. - // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be - // also defined at compile times of the SQLite library itself. - name0 = query.getColumn(0).getOriginName(); - name1 = query.getColumn(1).getOriginName(); - name2 = query.getColumn(2).getOriginName(); - std::cout << "origin table 'test' [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; -#endif - bFirst = false; - } - std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n"; + // Demonstrates that inserting column value in a std:ostream is natural + std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; } // Reset the query to use it again @@ -143,11 +145,16 @@ int main () // Bind the string value "6" to the first parameter of the SQL query query.bind(1, "6"); std::cout << "binded with string value \"6\" :\n"; - + // Reuses variables: uses assignement operator in the loop instead of constructor with initialization + int id = 0; + std::string value2; + double weight = 0.0; while (query.executeStep()) { - // Demonstrate that inserting column value in a std:ostream is natural - std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; + id = query.getColumn(0); // = query.getColumn(0).getInt() + value2 = query.getColumn(1); // = query.getColumn(1).getText() + weight = query.getColumn(2); // = query.getColumn(1).getInt() + std::cout << "row (" << id << ", \"" << value2 << "\", " << weight << ")\n"; } } catch (std::exception& e) @@ -163,7 +170,7 @@ int main () // Open the database and compile the query Example example; - // Demonstrate the way to use the same query with different parameter values + // Demonstrates the way to use the same query with different parameter values example.ListGreaterThan(8); example.ListGreaterThan(6); example.ListGreaterThan(2); From 6a2f8a6a8b710b8d110b6f06ce2f8643cd40081a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Wed, 29 Apr 2015 10:12:13 +0200 Subject: [PATCH 07/33] Cleanup and comment on the overload for GCC & Clang --- include/SQLiteCpp/Column.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 34ed4d1..b2f89ca 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -66,7 +66,7 @@ public: */ const char* getName() const noexcept; // nothrow - #ifdef SQLITE_ENABLE_COLUMN_METADATA +#ifdef SQLITE_ENABLE_COLUMN_METADATA /** * @brief Return a pointer to the table column name that is the origin of this result column * @@ -184,8 +184,9 @@ public: { return getBlob(); } -#ifdef __GNUC__ - // NOTE : the following is required by GCC to cast a Column result in a std::string + +#ifdef __GNUC__ + // NOTE : the following is required by GCC and Clang to cast a Column result in a std::string // (error: conversion from ‘SQLite::Column’ to non-scalar type ‘std::string {aka std::basic_string}’) // but is not working under Microsoft Visual Studio 2010 and 2012 // (error C2440: 'initializing' : cannot convert from 'SQLite::Column' to 'std::basic_string<_Elem,_Traits,_Ax>' From d45ec996a34d2f129c11564fc4c72845de366595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Wed, 29 Apr 2015 10:19:19 +0200 Subject: [PATCH 08/33] Fix the example overload pb on GCC & Clang - uses explicit getText() instead of implicit conversion --- examples/example1/main.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index b243664..e94603a 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -123,11 +123,11 @@ int main () std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n"; #endif // Demonstrates how to get some typed column value (and the equivalent explicit call) - const int id = query.getColumn(0); // = query.getColumn(0).getInt() - //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText() - const std::string value2 = query.getColumn(1); // = query.getColumn(1).getText() + const int id = query.getColumn(0); // = query.getColumn(0).getInt(); + //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText(); + const std::string value2 = query.getColumn(1); // = query.getColumn(1).getText(); const int bytes = query.getColumn(1).getBytes(); - const double weight = query.getColumn(2); // = query.getColumn(2).getInt() + const double weight = query.getColumn(2); // = query.getColumn(2).getInt(); std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n"; } @@ -151,9 +151,9 @@ int main () double weight = 0.0; while (query.executeStep()) { - id = query.getColumn(0); // = query.getColumn(0).getInt() - value2 = query.getColumn(1); // = query.getColumn(1).getText() - weight = query.getColumn(2); // = query.getColumn(1).getInt() + id = query.getColumn(0).getInt(); + value2 = query.getColumn(1).getText(); + weight = query.getColumn(2).getInt(); std::cout << "row (" << id << ", \"" << value2 << "\", " << weight << ")\n"; } } From 7fbfc2967779d0baff53ac09603b4c053ce6473d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Thu, 30 Apr 2015 18:39:46 +0200 Subject: [PATCH 09/33] Fix #23 optimized Statement::getColumn() by name - fix Statement::getColumn(apName) provided by #45 but was not working instead of using #46 that conflicts with current master - rework it by using a map of columns name as a cache populated the first time the method is called - add corresponding Unit Test --- include/SQLiteCpp/Statement.h | 21 +++++++++++------- src/Statement.cpp | 24 ++++++++++++++------ tests/Statement_test.cpp | 42 +++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 7dab620..5b620b3 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -12,6 +12,7 @@ #include #include +#include #include @@ -274,7 +275,8 @@ public: * Can be used to access the data of the current row of result when applicable, * while the executeStep() method returns true. * - * Throw an exception if there is no row to return a Column from : + * Throw an exception if there is no row to return a Column from: + * - if provided index is out of bound * - before any executeStep() call * - after the last executeStep() returned false * - after a reset() call @@ -283,8 +285,7 @@ public: * * @param[in] aIndex Index of the column, starting at 0 * - * @note This method is no more const, starting in v0.5, - * which reflects the fact that the returned Column object will + * @note This method is not const, reflecting the fact that the returned Column object will * share the ownership of the underlying sqlite3_stmt. * * @warning The resulting Column object must not be memorized "as-is". @@ -302,16 +303,16 @@ public: * while the executeStep() method returns true. * * Throw an exception if there is no row to return a Column from : + * - if provided name is not one of the aliased column names * - before any executeStep() call * - after the last executeStep() returned false * - after a reset() call * * Throw an exception if the specified index is out of the [0, getColumnCount()) range. * - * @param[in] aName Name of the column, starting at index 0 + * @param[in] apName Name of the column, starting at index 0 * - * @note This method is no more const, starting in v0.5, - * which reflects the fact that the returned Column object will + * @note This method is not const, reflecting the fact that the returned Column object will * share the ownership of the underlying sqlite3_stmt. * * @warning The resulting Column object must not be memorized "as-is". @@ -320,8 +321,8 @@ public: * Thus, you should instead extract immediately its data (getInt(), getText()...) * and use or copy this data for any later usage. */ - Column getColumn(const char* aName); - + Column getColumn(const char* apName); + /** * @brief Test if the column value is NULL * @@ -424,10 +425,14 @@ private: */ void check(const int aRet); +private: + typedef std::map TColumnNames; + private: std::string mQuery; //!< UTF-8 SQL Query Ptr mStmtPtr; //!< Shared Pointer to the prepared SQLite Statement Object int mColumnCount; //!< Number of columns in the result of the prepared statement + TColumnNames mColumnNames; //!< Map of columns index by name bool mbOk; //!< true when a row has been fetched with executeStep() bool mbDone; //!< true when the last executeStep() had no more row to fetch }; diff --git a/src/Statement.cpp b/src/Statement.cpp index 4b5a73e..597fcae 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -258,9 +258,9 @@ Column Statement::getColumn(const int 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* aName) +Column Statement::getColumn(const char* apName) { - int aIndex = -1; + int Index = -1; if (false == mbOk) { @@ -268,18 +268,28 @@ Column Statement::getColumn(const char* aName) } else { - for (int i = 0; i < mColumnCount; ++i) { - if (sqlite3_column_name(mStmtPtr, i) == aName) - break; + if (mColumnNames.empty()) + { + for (int i = 0; i < mColumnCount; ++i) + { + const char* pName = sqlite3_column_name(mStmtPtr, i); + mColumnNames[pName] = i; + } } - if ((aIndex < 0) || (aIndex >= mColumnCount)) { + const TColumnNames::const_iterator iIndex = mColumnNames.find(apName); + if (iIndex != mColumnNames.end()) + { + Index = (*iIndex).second; + } + else + { throw SQLite::Exception("Column index out of range"); } } // Share the Statement Object handle with the new Column created - return Column(mStmtPtr, aIndex); + return Column(mStmtPtr, Index); } // Test if the column is NULL diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 4a16dc1..55898be 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -77,3 +77,45 @@ TEST(Statement, invalid) { } // Close DB test.db3 remove("test.db3"); } + + +TEST(Statement, getColumnByName) { + remove("test.db3"); + { + // Create a new database + SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + + // Create a new table + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)")); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + + // Create a first row + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)")); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + // Compile a SQL query + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(4, query.getColumnCount()); + query.executeStep(); + EXPECT_TRUE (query.isOk()); + EXPECT_FALSE(query.isDone()); + + // Look for unexisting columns + EXPECT_THROW(query.getColumn("unknown"), SQLite::Exception); + EXPECT_THROW(query.getColumn(""), SQLite::Exception); + + const std::string msg = query.getColumn("msg"); + const int integer = query.getColumn("int"); + const double real = query.getColumn("double"); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + + } // Close DB test.db3 + remove("test.db3"); +} From 416958f094db23bf60e575cf9766963dd7f869ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Thu, 30 Apr 2015 22:08:26 +0200 Subject: [PATCH 10/33] Add sample code for getColumn() by name - sample code provided by #46 - rework example for better readability --- examples/example1/main.cpp | 87 +++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index e94603a..92cb7d2 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -83,7 +83,8 @@ private: int main () { - // Basic example (1/6) : + //////////////////////////////////////////////////////////////////////////// + // Very basic first example (1/7) : try { // Open a database file in readonly mode @@ -95,8 +96,24 @@ int main () std::cout << "SQLite table 'test' exists=" << bExists << "\n"; // Get a single value result with an easy to use shortcut - std::string value = db.execAndGet("SELECT value FROM test WHERE id=2"); + const std::string value = db.execAndGet("SELECT value FROM test WHERE id=2"); std::cout << "execAndGet=" << value.c_str() << std::endl; + } + catch (std::exception& e) + { + std::cout << "SQLite exception: " << e.what() << std::endl; + return EXIT_FAILURE; // unexpected error : exit the example program + } + + //////////////////////////////////////////////////////////////////////////// + // Simple select query - few variations (2/7) : + try + { + // Open a database file in readonly mode + SQLite::Database db(filename_example_db3); // SQLITE_OPEN_READONLY + std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n"; + + ///// a) Loop to get values of column by index, using auto cast to variable type // Compile a SQL query, containing one parameter (index 1) SQLite::Statement query(db, "SELECT id as test_id, value as test_val, weight as test_weight FROM test WHERE weight > ?"); @@ -105,6 +122,24 @@ int main () query.bind(1, 2); std::cout << "binded with integer value '2' :\n"; + // Loop to execute the query step by step, to get one a row of results at a time + while (query.executeStep()) + { + // Demonstrates how to get some typed column value (and the equivalent explicit call) + const int id = query.getColumn(0); // = query.getColumn(0).getInt(); + //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText(); + const std::string value = query.getColumn(1); // = query.getColumn(1).getText(); + const int bytes = query.getColumn(1).size(); // .getColumn(1).getBytes(); + const double weight = query.getColumn(2); // = query.getColumn(2).getInt(); + std::cout << "row (" << id << ", \"" << value.c_str() << "\"(" << bytes << ") " << weight << ")\n"; + } + + ///// b) Get aliased column names (and original column names if possible) + + // Reset the query to use it again + query.reset(); + std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount() << " columns in the result)\n"; + // Execute the first step of the query, to get the fist row of results, and name of columns if (query.executeStep()) { @@ -113,6 +148,7 @@ int main () const std::string name1 = query.getColumn(1).getName(); const std::string name2 = query.getColumn(2).getName(); std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; + #ifdef SQLITE_ENABLE_COLUMN_METADATA // Show how to get origin names of the table columns from which theses result columns come from. // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be @@ -122,23 +158,34 @@ int main () const std::string oname2 = query.getColumn(2).getOriginName(); std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n"; #endif - // Demonstrates how to get some typed column value (and the equivalent explicit call) - const int id = query.getColumn(0); // = query.getColumn(0).getInt(); - //const char* pvalue = query.getColumn(1); // = query.getColumn(1).getText(); - const std::string value2 = query.getColumn(1); // = query.getColumn(1).getText(); - const int bytes = query.getColumn(1).getBytes(); - const double weight = query.getColumn(2); // = query.getColumn(2).getInt(); - - std::cout << "row (" << id << ", \"" << value2.c_str() << "\" " << bytes << " bytes, " << weight << ")\n"; + // Demonstrates that inserting column value in a std:ostream is natural + std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; } - - // Loop to execute the query step by step, to get one a row of results at a time + // Loop to execute the rest of the query step by step, to get one a row of results at a time while (query.executeStep()) { // Demonstrates that inserting column value in a std:ostream is natural std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; } + ///// c) Get columns by name + + // Reset the query to use it again + query.reset(); + std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount() << " columns in the result)\n"; + + // Loop to execute the query step by step, to get one a row of results at a time + while (query.executeStep()) + { + // Demonstrates how to get column value by aliased name (not the original table names, see above) + const int id = query.getColumn("test_id"); + const std::string value = query.getColumn("test_val"); + const double weight = query.getColumn("test_weight"); + std::cout << "row (" << id << ", \"" << value.c_str() << "\" " << weight << ")\n"; + } + + ///// d) Uses explicit typed getters instead of auto cast operators + // Reset the query to use it again query.reset(); std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount () << " columns in the result)\n"; @@ -147,14 +194,14 @@ int main () std::cout << "binded with string value \"6\" :\n"; // Reuses variables: uses assignement operator in the loop instead of constructor with initialization int id = 0; - std::string value2; + std::string value; double weight = 0.0; while (query.executeStep()) { id = query.getColumn(0).getInt(); - value2 = query.getColumn(1).getText(); + value = query.getColumn(1).getText(); weight = query.getColumn(2).getInt(); - std::cout << "row (" << id << ", \"" << value2 << "\", " << weight << ")\n"; + std::cout << "row (" << id << ", \"" << value << "\", " << weight << ")\n"; } } catch (std::exception& e) @@ -164,7 +211,7 @@ int main () } //////////////////////////////////////////////////////////////////////////// - // Object Oriented Basic example (2/6) : + // Object Oriented Basic example (3/7) : try { // Open the database and compile the query @@ -181,7 +228,7 @@ int main () return EXIT_FAILURE; // unexpected error : exit the example program } - // The execAndGet wrapper example (3/6) : + // The execAndGet wrapper example (4/7) : try { // Open a database file in readonly mode @@ -201,7 +248,7 @@ int main () } //////////////////////////////////////////////////////////////////////////// - // Simple batch queries example (4/6) : + // Simple batch queries example (5/7) : try { // Open a database file in create/write mode @@ -242,7 +289,7 @@ int main () remove("test.db3"); //////////////////////////////////////////////////////////////////////////// - // RAII transaction example (5/6) : + // RAII transaction example (6/7) : try { // Open a database file in create/write mode @@ -310,7 +357,7 @@ int main () remove("transaction.db3"); //////////////////////////////////////////////////////////////////////////// - // Binary blob and in-memory database example (6/6) : + // Binary blob and in-memory database example (7/7) : try { // Open a database file in create/write mode From b10bf6faa54d78fa12f0aed08ff32e7b43c1a59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Thu, 30 Apr 2015 22:17:16 +0200 Subject: [PATCH 11/33] Re-enable SQLITE_ENABLE_COLUMN_METADATA by default under Windows - cleanup to the CMakeLists.txt for better readability --- CMakeLists.txt | 94 ++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 226e8dc..6f86305 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,47 +1,27 @@ -if (BIICODE) - - # biicode doesn't process files bigger than 5Mb - list(APPEND BII_LIB_SRC sqlite3/sqlite3.c) - # Include base block dir - ADD_BIICODE_TARGETS() - - # Link target with dl for linux - if (UNIX) - TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE pthread) - if(NOT APPLE) - TARGET_LINK_LIBRARIES(${BII_BLOCK_TARGET} INTERFACE dl) - endif() - endif() -else (BIICODE) - # Main CMake file for compiling the library itself, examples and tests. # # Copyright (c) 2012-2015 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) +if (NOT BIICODE) + cmake_minimum_required(VERSION 2.6) project(SQLiteCpp) -option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getName(). Require support from sqlite3 library." OFF) -if (SQLITE_ENABLE_COLUMN_METADATA) - # Enable the use of SQLite column metadata and Column::getName() method, - # Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X). - add_definitions(-DSQLITE_ENABLE_COLUMN_METADATA) -endif (SQLITE_ENABLE_COLUMN_METADATA) - -option(SQLITE_ENABLE_ASSERT_HANDLER "Enable the user defintion of a assertion_failed() handler." OFF) -if (SQLITE_ENABLE_ASSERT_HANDLER) - # Enable the user defintion of a assertion_failed() handler (default to false, easier to handler for begginers). - add_definitions(-DSQLITECPP_ENABLE_ASSERT_HANDLER) -endif (SQLITE_ENABLE_ASSERT_HANDLER) - # Define useful variables to handle OS differences: if (WIN32) set(DEV_NULL "NUL") + # build the SQLite3 C library for Windows (for ease of use) + set(SQLITECPP_INTERNAL_SQLITE_DEFAULT ON) + set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT OFF) else (WIN32) set(DEV_NULL "/dev/null") + # do not build the SQLite3 C library, but uses the Linux/Mac OS X sqlite3-dev package + set(SQLITECPP_INTERNAL_SQLITE_DEFAULT OFF) + set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON) endif (WIN32) + # then Compiler/IDE differences: if (MSVC) set(CPPLINT_ARG_OUTPUT "--output=vs7") @@ -69,6 +49,22 @@ set(CPPLINT_ARG_VERBOSE "--verbose=3") set(CPPLINT_ARG_LINELENGTH "--linelength=120") +# Options relative to SQLite and SQLiteC++ functions + +option(SQLITE_ENABLE_COLUMN_METADATA "Enable Column::getName(). Require support from sqlite3 library." ${SQLITE_ENABLE_COLUMN_METADATA_DEFAULT}) +if (SQLITE_ENABLE_COLUMN_METADATA) + # Enable the use of SQLite column metadata and Column::getName() method, + # Require that the sqlite3 library is also compiled with this flag (default under Debian/Ubuntu, but not on Mac OS X). + add_definitions(-DSQLITE_ENABLE_COLUMN_METADATA) +endif (SQLITE_ENABLE_COLUMN_METADATA) + +option(SQLITE_ENABLE_ASSERT_HANDLER "Enable the user defintion of a assertion_failed() handler." OFF) +if (SQLITE_ENABLE_ASSERT_HANDLER) + # Enable the user defintion of a assertion_failed() handler (default to false, easier to handler for begginers). + add_definitions(-DSQLITECPP_ENABLE_ASSERT_HANDLER) +endif (SQLITE_ENABLE_ASSERT_HANDLER) + + ## Core source code ## # adding a new file require explicittly modifing the CMakeLists.txt @@ -141,16 +137,14 @@ if (UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Cla endif (UNIX AND (CMAKE_COMPILER_IS_GNUCXX OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")) -# SQLite3 library (Windows only) +# SQLite3 library (mostly usefull under Windows) -option (SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ON) -if (WIN32) - if (SQLITECPP_INTERNAL_SQLITE) - # build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package - add_subdirectory(sqlite3) - include_directories("${PROJECT_SOURCE_DIR}/sqlite3") - endif (SQLITECPP_INTERNAL_SQLITE) -endif (WIN32) +option(SQLITECPP_INTERNAL_SQLITE "Add the internal SQLite3 source to the project." ${SQLITECPP_INTERNAL_SQLITE_DEFAULT}) +if (SQLITECPP_INTERNAL_SQLITE) + # build the SQLite3 C library for Windows (for ease of use) versus Linux sqlite3-dev package + add_subdirectory(sqlite3) + include_directories("${PROJECT_SOURCE_DIR}/sqlite3") +endif (SQLITECPP_INTERNAL_SQLITE) # Optional additional targets: @@ -206,9 +200,9 @@ if (SQLITECPP_BUILD_EXAMPLES) # add the basic example executable add_executable(SQLiteCpp_example1 ${SQLITECPP_EXAMPLES}) target_link_libraries(SQLiteCpp_example1 SQLiteCpp sqlite3) -else(SQLITECPP_BUILD_EXAMPLES) +else (SQLITECPP_BUILD_EXAMPLES) message(STATUS "SQLITECPP_BUILD_EXAMPLES OFF") -endif(SQLITECPP_BUILD_EXAMPLES) +endif (SQLITECPP_BUILD_EXAMPLES) option(SQLITECPP_BUILD_TESTS "Build and run tests." OFF) if (SQLITECPP_BUILD_TESTS) @@ -233,9 +227,25 @@ if (SQLITECPP_BUILD_TESTS) if (SQLITECPP_BUILD_EXAMPLES) # does the example1 runs successfully? add_test(Example1Run SQLiteCpp_example1) - endif(SQLITECPP_BUILD_EXAMPLES) + endif (SQLITECPP_BUILD_EXAMPLES) else (SQLITECPP_BUILD_TESTS) message(STATUS "SQLITECPP_BUILD_TESTS OFF") endif (SQLITECPP_BUILD_TESTS) -endif (BIICODE) + +else (NOT BIICODE) + + # biicode doesn't process files bigger than 5Mb + list(APPEND BII_LIB_SRC sqlite3/sqlite3.c) + # Include base block dir + ADD_BIICODE_TARGETS() + + # Link target with dl for linux + if (UNIX) + target_link_libraries(${BII_BLOCK_TARGET} INTERFACE pthread) + if (NOT APPLE) + target_link_libraries(${BII_BLOCK_TARGET} INTERFACE dl) + endif () + endif () + +endif (NOT BIICODE) From 18620457b14d0e718da633f6a3164f27b0313406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Thu, 30 Apr 2015 22:20:13 +0200 Subject: [PATCH 12/33] Add some more Unit Tests --- CMakeLists.txt | 1 + include/SQLiteCpp/Assertion.h | 2 +- include/SQLiteCpp/Column.h | 2 +- src/Column.cpp | 4 +-- tests/Column_test.cpp | 58 +++++++++++++++++++++++++++++++++++ tests/Database_test.cpp | 3 ++ tests/Statement_test.cpp | 15 +++++++-- 7 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 tests/Column_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f86305..f9d1817 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,7 @@ source_group(inc FILES ${SQLITECPP_INC}) # list of test files of the library set(SQLITECPP_TESTS + tests/Column_test.cpp tests/Database_test.cpp tests/Statement_test.cpp ) diff --git a/include/SQLiteCpp/Assertion.h b/include/SQLiteCpp/Assertion.h index ae611ab..2486e9e 100644 --- a/include/SQLiteCpp/Assertion.h +++ b/include/SQLiteCpp/Assertion.h @@ -40,7 +40,7 @@ namespace SQLite #else // if no assert handler provided by user code, use standard assert() -// (note: in debug mode, assert() does nothing) +// (note: in release mode assert() does nothing) #define SQLITECPP_ASSERT(expression, message) assert(expression && message) #endif diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index b2f89ca..a7c7e01 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -185,7 +185,7 @@ public: return getBlob(); } -#ifdef __GNUC__ +#ifdef __GNUC__ // NOTE : the following is required by GCC and Clang to cast a Column result in a std::string // (error: conversion from ‘SQLite::Column’ to non-scalar type ‘std::string {aka std::basic_string}’) // but is not working under Microsoft Visual Studio 2010 and 2012 diff --git a/src/Column.cpp b/src/Column.cpp index a679d15..c0b7991 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -31,14 +31,14 @@ Column::~Column() noexcept // nothrow } // Return the named assigned to this result column (potentially aliased) -const char * Column::getName() const noexcept // nothrow +const char* Column::getName() const noexcept // nothrow { return sqlite3_column_name(mStmtPtr, 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 // nothrow +const char* Column::getOriginName() const noexcept // nothrow { return sqlite3_column_origin_name(mStmtPtr, mIndex); } diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp new file mode 100644 index 0000000..8f0646f --- /dev/null +++ b/tests/Column_test.cpp @@ -0,0 +1,58 @@ +/** + * @file Column_test.cpp + * @ingroup tests + * @brief Test of a SQLiteCpp Column. + * + * Copyright (c) 2012-2015 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 + +#include + + +TEST(Column, basics) { + remove("test.db3"); + { + // Create a new database + SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + + // Create a new table + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)")); + EXPECT_TRUE(db.tableExists("test")); + EXPECT_TRUE(db.tableExists(std::string("test"))); + EXPECT_EQ(0, db.getLastInsertRowid()); + + // Create a first row + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)")); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + // Compile a SQL query + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(4, query.getColumnCount ()); + query.executeStep(); + EXPECT_TRUE (query.isOk()); + EXPECT_FALSE(query.isDone()); + + // TODO + const std::string msg = query.getColumn("msg"); + const int integer = query.getColumn("int"); + const double real = query.getColumn("double"); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + + } // Close DB test.db3 + remove("test.db3"); +} diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index c92f0fb..43e3e1d 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -115,6 +115,9 @@ TEST(Database, exec) { EXPECT_EQ(9, db.getTotalChanges()); #endif + // Add a row with too many values (more than rows in the table) + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); + } // Close DB test.db3 remove("test.db3"); } diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 55898be..19fd48b 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -53,7 +53,7 @@ TEST(Statement, invalid) { EXPECT_FALSE(query.isOk()); EXPECT_FALSE(query.isDone()); - query.exec(); + query.executeStep(); EXPECT_FALSE(query.isOk()); EXPECT_TRUE( query.isDone()); query.reset(); @@ -70,10 +70,21 @@ TEST(Statement, invalid) { EXPECT_EQ(SQLITE_RANGE, db.getErrorCode()); EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode()); - query.exec(); + query.exec(); // exec() instead of executeStep() as there is no result EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); EXPECT_THROW(query.getColumn(0), SQLite::Exception); + // Add a first row + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + query.reset(); + EXPECT_FALSE(query.isOk()); + EXPECT_FALSE(query.isDone()); + + EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result + } // Close DB test.db3 remove("test.db3"); } From acaed41465b9d343c15e511ba0729b2476b18d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 11:50:50 +0200 Subject: [PATCH 13/33] setBusyTimeout() now uses check() and throw in case of error - add unit test for setBusyTimeout() - add unit test for in memory databases --- include/SQLiteCpp/Database.h | 27 +++-- src/Database.cpp | 33 ++++-- tests/Database_test.cpp | 216 +++++++++++++++++++---------------- 3 files changed, 160 insertions(+), 116 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 7cc44a8..39ae312 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -59,7 +59,7 @@ public: * * @throw SQLite::Exception in case of error */ - Database(const char* apFilename, const int aFlags = SQLITE_OPEN_READONLY, const char * apVfs = NULL); + Database(const char* apFilename, const int aFlags = SQLITE_OPEN_READONLY, const char* apVfs = NULL); /** * @brief Open the provided database UTF-8 filename. @@ -89,6 +89,21 @@ public: */ virtual ~Database() noexcept; // nothrow + /** + * @brief Set a busy handler that sleeps for a specified amount of time when a table is locked. + * + * This is usefull in multithreaded program to handle case where a table is locked for writting by a thread. + * Any other thread cannot access the table and will receive a SQLITE_BUSY error: + * setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error. + * Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;". + * Default busy timeout is 0ms. + * + * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY + * + * @throw SQLite::Exception in case of error + */ + void setBusyTimeout(int aTimeoutMs) noexcept; // nothrow + /** * @brief Shortcut to execute one or multiple statements without results. * @@ -206,16 +221,6 @@ public: return tableExists(aTableName.c_str()); } - /** - * @brief Set a busy handler that sleeps for a specified amount of time when a table is locked. - * - * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY - */ - inline int setBusyTimeout(int aTimeoutMs) noexcept // nothrow - { - return sqlite3_busy_timeout(mpSQLite, aTimeoutMs); - } - /** * @brief Get the rowid of the most recent successful INSERT into the database from the current connection. * diff --git a/src/Database.cpp b/src/Database.cpp index 11fcc8d..ba5e355 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -30,7 +30,7 @@ Database::Database(const char* apFilename, const int aFlags /*= SQLITE_OPEN_READ mpSQLite(NULL), mFilename(apFilename) { - int ret = sqlite3_open_v2(apFilename, &mpSQLite, aFlags, apVfs); + const int ret = sqlite3_open_v2(apFilename, &mpSQLite, aFlags, apVfs); if (SQLITE_OK != ret) { std::string strerr = sqlite3_errmsg(mpSQLite); @@ -44,7 +44,7 @@ Database::Database(const std::string& aFilename, const int aFlags /*= SQLITE_OPE mpSQLite(NULL), mFilename(aFilename) { - int ret = sqlite3_open_v2(aFilename.c_str(), &mpSQLite, aFlags, aVfs.empty() ? NULL : aVfs.c_str()); + const int ret = sqlite3_open_v2(aFilename.c_str(), &mpSQLite, aFlags, aVfs.empty() ? NULL : aVfs.c_str()); if (SQLITE_OK != ret) { std::string strerr = sqlite3_errmsg(mpSQLite); @@ -56,15 +56,34 @@ Database::Database(const std::string& aFilename, const int aFlags /*= SQLITE_OPE // Close the SQLite database connection. Database::~Database() noexcept // nothrow { - int ret = sqlite3_close(mpSQLite); + const int ret = sqlite3_close(mpSQLite); // Never throw an exception in a destructor SQLITECPP_ASSERT(SQLITE_OK == ret, sqlite3_errmsg(mpSQLite)); // See SQLITECPP_ENABLE_ASSERT_HANDLER } +/** + * @brief Set a busy handler that sleeps for a specified amount of time when a table is locked. + * + * This is usefull in multithreaded program to handle case where a table is locked for writting by a thread. + * Any other thread cannot access the table and will receive a SQLITE_BUSY error: + * setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error. + * Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;". + * Default busy timeout is 0ms. + * + * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY + * + * @throw SQLite::Exception in case of error + */ +void Database::setBusyTimeout(int aTimeoutMs) noexcept // nothrow +{ + const int ret = sqlite3_busy_timeout(mpSQLite, aTimeoutMs); + check(ret); +} + // Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...). int Database::exec(const char* apQueries) { - int ret = sqlite3_exec(mpSQLite, apQueries, NULL, NULL, NULL); + const int ret = sqlite3_exec(mpSQLite, apQueries, NULL, NULL, NULL); check(ret); // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only) @@ -90,7 +109,7 @@ bool Database::tableExists(const char* apTableName) Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?"); query.bind(1, apTableName); (void)query.executeStep(); // Cannot return false, as the above query always return a result - int Nb = query.getColumn(0); + const int Nb = query.getColumn(0); return (1 == Nb); } @@ -119,8 +138,8 @@ void Database::createFunction(const char* apFuncName, if (abDeterministic) { TextRep = TextRep|SQLITE_DETERMINISTIC; } - int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep, - apApp, apFunc, apStep, apFinal, apDestroy); + const int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep, + apApp, apFunc, apStep, apFinal, apDestroy); check(ret); } diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 43e3e1d..edb9235 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -54,125 +54,145 @@ TEST(Database, ctorExecCreateDropExist) { remove("test.db3"); } -TEST(Database, exec) { - remove("test.db3"); +TEST(Database, inMemory) { { // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); - - // Create a new table with an explicit "id" column aliasing the underlying rowid - // 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.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.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(2, db.getLastInsertRowid()); - EXPECT_EQ(2, db.getTotalChanges()); - - // third row : insert the "third" text value into new row of id 3 - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); - 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(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(3, db.getLastInsertRowid()); - EXPECT_EQ(5, db.getTotalChanges()); - - // drop the whole table, ie the two remaining columns - // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "DROP TABLE" statements - db.exec("DROP TABLE IF EXISTS test"); + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE); EXPECT_FALSE(db.tableExists("test")); - EXPECT_EQ(5, db.getTotalChanges()); - - // Re-Create the same table - // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "CREATE TABLE" statements db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); - EXPECT_EQ(5, db.getTotalChanges()); + EXPECT_TRUE(db.tableExists("test")); + // Create a new database: not shared with the above db + SQLite::Database db2(":memory:"); + EXPECT_FALSE(db2.tableExists("test")); + } // Close an destroy DBs + { + // Create a new database: no more "test" table + SQLite::Database db(":memory:"); + EXPECT_FALSE(db.tableExists("test")); + } // Close an destroy DB +} - // 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(2, db.getLastInsertRowid()); - EXPECT_EQ(7, db.getTotalChanges()); +TEST(Database, busyTimeout) { + // Create a new database + SQLite::Database db(":memory:"); + // Busy timeout default to 0ms: any contention between threads or process leads to SQLITE_BUSY error + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Set a non null busy timeout: any contention between threads will leads to as much retry as possible during the time + db.setBusyTimeout(5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Reset timeout to null + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); +} + +TEST(Database, exec) { + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE); + + // Create a new table with an explicit "id" column aliasing the underlying rowid + // 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.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.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(2, db.getLastInsertRowid()); + EXPECT_EQ(2, db.getTotalChanges()); + + // third row : insert the "third" text value into new row of id 3 + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); + 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(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(3, db.getLastInsertRowid()); + EXPECT_EQ(5, db.getTotalChanges()); + + // drop the whole table, ie the two remaining columns + // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "DROP TABLE" statements + db.exec("DROP TABLE IF EXISTS test"); + EXPECT_FALSE(db.tableExists("test")); + EXPECT_EQ(5, db.getTotalChanges()); + + // Re-Create the same table + // NOTE: here exec() returns 1, like the last time, as it is an undefined value for "CREATE TABLE" statements + db.exec("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 + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\");INSERT INTO test VALUES (NULL, \"second\");")); + 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(4, db.getLastInsertRowid()); - EXPECT_EQ(9, db.getTotalChanges()); + // 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(4, db.getLastInsertRowid()); + EXPECT_EQ(9, db.getTotalChanges()); #endif - // Add a row with too many values (more than rows in the table) - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); - - } // Close DB test.db3 - remove("test.db3"); + // Add a row with too many values (more than rows in the table) + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); } TEST(Database, execAndGet) { - remove("test.db3"); - { - // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE); - // Create a new table with an explicit "id" column aliasing the underlying rowid - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); + // Create a new table with an explicit "id" column aliasing the underlying rowid + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); - // insert a few rows - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\", 5)")); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\", 7)")); + // insert a few rows + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\", 5)")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\", 7)")); - // Get a single value result with an easy to use shortcut - EXPECT_STREQ("second", db.execAndGet("SELECT value FROM test WHERE id=2")); - EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7")); - EXPECT_EQ(3, (int)db.execAndGet("SELECT weight FROM test WHERE value=\"first\"")); - } // Close DB test.db3 - remove("test.db3"); + // Get a single value result with an easy to use shortcut + EXPECT_STREQ("second", db.execAndGet("SELECT value FROM test WHERE id=2")); + EXPECT_STREQ("third", db.execAndGet("SELECT value FROM test WHERE weight=7")); + EXPECT_EQ(3, db.execAndGet("SELECT weight FROM test WHERE value=\"first\"").getInt()); } TEST(Database, execException) { - remove("test.db3"); - { - // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // exception with SQL error: "no such table" - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + // exception with SQL error: "no such table" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - // Create a new table - db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + // Create a new table + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT, weight INTEGER)"); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // exception with SQL error: "table test has 3 columns but 2 values were supplied" - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 3)"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + // exception with SQL error: "table test has 3 columns but 2 values were supplied" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, 3)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - // exception with SQL error: "No row to get a column from" - EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"first\""), SQLite::Exception); + // exception with SQL error: "No row to get a column from" + EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"first\""), SQLite::Exception); - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); - // exception with SQL error: "No row to get a column from" - EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception); - } // Close DB test.db3 - remove("test.db3"); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); + // exception with SQL error: "No row to get a column from" + EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception); } From 59ceff2ec2647839347a98ddc5574d637b207d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 12:48:04 +0200 Subject: [PATCH 14/33] Deactivate unit test for setBusyTimeout() not supported before SQLite 3.7.15 - sqlite3 3.7.9-2ubuntu1 of Ubuntu 12.04 used by Travis CI --- tests/Database_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index edb9235..d055cfc 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -72,6 +72,7 @@ TEST(Database, inMemory) { } // Close an destroy DB } +#if SQLITE_VERSION_NUMBER >= 3007015 // first version with PRAGMA busy_timeout TEST(Database, busyTimeout) { // Create a new database SQLite::Database db(":memory:"); @@ -86,6 +87,7 @@ TEST(Database, busyTimeout) { db.setBusyTimeout(0); EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); } +#endif // SQLITE_VERSION_NUMBER >= 3007015 TEST(Database, exec) { // Create a new database From abebb231c611eea2c3fbc6ebb8a2d6c5022c32aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 17:41:11 +0200 Subject: [PATCH 15/33] Add a new test to show persistence in file (on contrary to in memory databases) --- tests/Database_test.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index d055cfc..a693e3b 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -54,6 +54,26 @@ TEST(Database, ctorExecCreateDropExist) { remove("test.db3"); } +TEST(Database, createCloseReopen) { + remove("test.db3"); + { + // Try to open the unexisting database + EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); + + // Create a new database + SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_FALSE(db.tableExists("test")); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + { + // Reopen the database file + SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_TRUE(db.tableExists("test")); + } // Close DB test.db3 + remove("test.db3"); +} + TEST(Database, inMemory) { { // Create a new database @@ -147,9 +167,6 @@ TEST(Database, exec) { EXPECT_EQ(4, db.getLastInsertRowid()); EXPECT_EQ(9, db.getTotalChanges()); #endif - - // Add a row with too many values (more than rows in the table) - EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); } TEST(Database, execAndGet) { @@ -197,4 +214,11 @@ TEST(Database, execException) { EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 3)")); // exception with SQL error: "No row to get a column from" EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception); + + // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" + EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); } + +// TODO: test Database::createFunction() From 1ed18ce2734ead822c1b68915b24b7b96c99e216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 21:07:27 +0200 Subject: [PATCH 16/33] Add unit test about Column operators --- tests/Column_test.cpp | 101 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index 8f0646f..b46db5c 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -16,6 +16,7 @@ #include #include +#include TEST(Column, basics) { @@ -27,31 +28,109 @@ TEST(Column, basics) { EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); // Create a new table - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)")); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL, binary BLOB, empty TEXT)")); EXPECT_TRUE(db.tableExists("test")); EXPECT_TRUE(db.tableExists(std::string("test"))); EXPECT_EQ(0, db.getLastInsertRowid()); - // Create a first row - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)")); + // Create a first row (autoid: 1) with all kind of data and a null value + SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, \"first\", 123, 0.123, ?, NULL)"); + // Bind the blob value to the first parameter of the SQL query + char buffer[] = "blob"; + void* blob = &buffer; + int size = sizeof(buffer); + insert.bind(1, blob, size); + // Execute the one-step query to insert the row + EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(1, db.getLastInsertRowid()); EXPECT_EQ(1, db.getTotalChanges()); // Compile a SQL query SQLite::Statement query(db, "SELECT * FROM test"); EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); - EXPECT_EQ(4, query.getColumnCount ()); + EXPECT_EQ(6, query.getColumnCount ()); query.executeStep(); EXPECT_TRUE (query.isOk()); EXPECT_FALSE(query.isDone()); - // TODO - const std::string msg = query.getColumn("msg"); - const int integer = query.getColumn("int"); - const double real = query.getColumn("double"); - EXPECT_EQ("first", msg); - EXPECT_EQ(123, integer); - EXPECT_EQ(0.123, real); + // validates every variant of cast operators, and conversions of types + { + int64_t id = query.getColumn(0); // operator sqlite3_int64() + const char* ptxt = query.getColumn(1); // operator const char*() + const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) + const int integer = query.getColumn(2); // operator int() + const double real = query.getColumn(3); // operator double() + const void* pblob = query.getColumn(4); // operator void*() + const void* pempty = query.getColumn(5); // operator void*() + EXPECT_EQ(1, id); + EXPECT_STREQ("first", ptxt); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + EXPECT_EQ(0, memcmp("blob", pblob, size)); + EXPECT_EQ(NULL, pempty); + } + + // validates every variant of explicit getters + { + int64_t id = query.getColumn(0).getInt64(); + const char* ptxt = query.getColumn(1).getText(); + const std::string msg = query.getColumn(1).getText(); + const int integer = query.getColumn(2).getInt(); + const double real = query.getColumn(3).getDouble(); + const void* pblob = query.getColumn(1).getBlob(); + EXPECT_EQ(1, id); + EXPECT_STREQ("first", ptxt); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + EXPECT_EQ(0, memcmp("first", pblob, 5)); + } + + // Validate getBytes(), getType(), isInteger(), isNull()... + EXPECT_EQ(SQLITE_INTEGER, query.getColumn(0).getType()); + EXPECT_EQ(true, query.getColumn(0).isInteger()); + EXPECT_EQ(false, query.getColumn(0).isFloat()); + EXPECT_EQ(false, query.getColumn(0).isText()); + EXPECT_EQ(false, query.getColumn(0).isBlob()); + EXPECT_EQ(false, query.getColumn(0).isNull()); + EXPECT_EQ(1, query.getColumn(0).getBytes()); // size of the string "1" without the null terminator + EXPECT_EQ(SQLITE_TEXT, query.getColumn(1).getType()); + EXPECT_EQ(false, query.getColumn(1).isInteger()); + EXPECT_EQ(false, query.getColumn(1).isFloat()); + EXPECT_EQ(true, query.getColumn(1).isText()); + EXPECT_EQ(false, query.getColumn(1).isBlob()); + EXPECT_EQ(false, query.getColumn(1).isNull()); + EXPECT_EQ(5, query.getColumn(1).getBytes()); // size of the string "first" + EXPECT_EQ(SQLITE_INTEGER, query.getColumn(2).getType()); + EXPECT_EQ(true, query.getColumn(2).isInteger()); + EXPECT_EQ(false, query.getColumn(2).isFloat()); + EXPECT_EQ(false, query.getColumn(2).isText()); + EXPECT_EQ(false, query.getColumn(2).isBlob()); + EXPECT_EQ(false, query.getColumn(2).isNull()); + EXPECT_EQ(3, query.getColumn(2).getBytes()); // size of the string "123" + EXPECT_EQ(SQLITE_FLOAT, query.getColumn(3).getType()); + EXPECT_EQ(false, query.getColumn(3).isInteger()); + EXPECT_EQ(true, query.getColumn(3).isFloat()); + EXPECT_EQ(false, query.getColumn(3).isText()); + EXPECT_EQ(false, query.getColumn(3).isBlob()); + EXPECT_EQ(false, query.getColumn(3).isNull()); + EXPECT_EQ(5, query.getColumn(3).getBytes()); // size of the string "0.123" + EXPECT_EQ(5, query.getColumn(4).getBytes()); // size of the string "blob" with the null terminator + EXPECT_EQ(SQLITE_BLOB, query.getColumn(4).getType()); + EXPECT_EQ(false, query.getColumn(4).isInteger()); + EXPECT_EQ(false, query.getColumn(4).isFloat()); + EXPECT_EQ(false, query.getColumn(4).isText()); + EXPECT_EQ(true, query.getColumn(4).isBlob()); + EXPECT_EQ(false, query.getColumn(4).isNull()); + EXPECT_EQ(0, query.getColumn(5).getBytes()); // size of the string "" without the null terminator + EXPECT_EQ(SQLITE_NULL, query.getColumn(5).getType()); + EXPECT_EQ(false, query.getColumn(5).isInteger()); + EXPECT_EQ(false, query.getColumn(5).isFloat()); + EXPECT_EQ(false, query.getColumn(5).isText()); + EXPECT_EQ(false, query.getColumn(5).isBlob()); + EXPECT_EQ(true, query.getColumn(5).isNull()); } // Close DB test.db3 remove("test.db3"); From f407e4e4692755aad2f3cf30de2a43f2ea563c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 22:37:19 +0200 Subject: [PATCH 17/33] Add unit test for Column names - improve other Column unit test --- include/SQLiteCpp/Column.h | 2 + tests/Column_test.cpp | 252 ++++++++++++++++++++----------------- 2 files changed, 142 insertions(+), 112 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index a7c7e01..d16e6f7 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -63,6 +63,8 @@ public: /** * @brief Return a pointer to the named assigned to a result column (potentially aliased) + * + * @see getOriginName() to get original column name (not aliased) */ const char* getName() const noexcept; // nothrow diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index b46db5c..8e52781 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -19,119 +19,147 @@ #include -TEST(Column, basics) { - remove("test.db3"); +TEST(Column, basis) { + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + + // Create a new table + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL, binary BLOB, empty TEXT)")); + EXPECT_TRUE(db.tableExists("test")); + EXPECT_TRUE(db.tableExists(std::string("test"))); + EXPECT_EQ(0, db.getLastInsertRowid()); + + // Create a first row (autoid: 1) with all kind of data and a null value + SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, \"first\", 123, 0.123, ?, NULL)"); + // Bind the blob value to the first parameter of the SQL query + char buffer[] = "blob"; + void* blob = &buffer; + int size = sizeof(buffer); + insert.bind(1, blob, size); + // Execute the one-step query to insert the row + EXPECT_EQ(1, insert.exec()); + + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); + + // Compile a SQL query + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(6, query.getColumnCount ()); + query.executeStep(); + EXPECT_TRUE (query.isOk()); + EXPECT_FALSE(query.isDone()); + + // validates every variant of cast operators, and conversions of types { - // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + int64_t id = query.getColumn(0); // operator sqlite3_int64() + const char* ptxt = query.getColumn(1); // operator const char*() + const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) + const int integer = query.getColumn(2); // operator int() + const double real = query.getColumn(3); // operator double() + const void* pblob = query.getColumn(4); // operator void*() + const void* pempty = query.getColumn(5); // operator void*() + EXPECT_EQ(1, id); + EXPECT_STREQ("first", ptxt); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + EXPECT_EQ(0, memcmp("blob", pblob, size)); + EXPECT_EQ(NULL, pempty); + } - // Create a new table - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL, binary BLOB, empty TEXT)")); - EXPECT_TRUE(db.tableExists("test")); - EXPECT_TRUE(db.tableExists(std::string("test"))); - EXPECT_EQ(0, db.getLastInsertRowid()); + // validates every variant of explicit getters + { + int64_t id = query.getColumn(0).getInt64(); + const char* ptxt = query.getColumn(1).getText(); + const std::string msg = query.getColumn(1).getText(); + const int integer = query.getColumn(2).getInt(); + const double real = query.getColumn(3).getDouble(); + const void* pblob = query.getColumn(1).getBlob(); + EXPECT_EQ(1, id); + EXPECT_STREQ("first", ptxt); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); + EXPECT_EQ(0, memcmp("first", pblob, 5)); + } - // Create a first row (autoid: 1) with all kind of data and a null value - SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, \"first\", 123, 0.123, ?, NULL)"); - // Bind the blob value to the first parameter of the SQL query - char buffer[] = "blob"; - void* blob = &buffer; - int size = sizeof(buffer); - insert.bind(1, blob, size); - // Execute the one-step query to insert the row - EXPECT_EQ(1, insert.exec()); - - EXPECT_EQ(1, db.getLastInsertRowid()); - EXPECT_EQ(1, db.getTotalChanges()); - - // Compile a SQL query - SQLite::Statement query(db, "SELECT * FROM test"); - EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); - EXPECT_EQ(6, query.getColumnCount ()); - query.executeStep(); - EXPECT_TRUE (query.isOk()); - EXPECT_FALSE(query.isDone()); - - // validates every variant of cast operators, and conversions of types - { - int64_t id = query.getColumn(0); // operator sqlite3_int64() - const char* ptxt = query.getColumn(1); // operator const char*() - const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) - const int integer = query.getColumn(2); // operator int() - const double real = query.getColumn(3); // operator double() - const void* pblob = query.getColumn(4); // operator void*() - const void* pempty = query.getColumn(5); // operator void*() - EXPECT_EQ(1, id); - EXPECT_STREQ("first", ptxt); - EXPECT_EQ("first", msg); - EXPECT_EQ(123, integer); - EXPECT_EQ(0.123, real); - EXPECT_EQ(0, memcmp("blob", pblob, size)); - EXPECT_EQ(NULL, pempty); - } - - // validates every variant of explicit getters - { - int64_t id = query.getColumn(0).getInt64(); - const char* ptxt = query.getColumn(1).getText(); - const std::string msg = query.getColumn(1).getText(); - const int integer = query.getColumn(2).getInt(); - const double real = query.getColumn(3).getDouble(); - const void* pblob = query.getColumn(1).getBlob(); - EXPECT_EQ(1, id); - EXPECT_STREQ("first", ptxt); - EXPECT_EQ("first", msg); - EXPECT_EQ(123, integer); - EXPECT_EQ(0.123, real); - EXPECT_EQ(0, memcmp("first", pblob, 5)); - } - - // Validate getBytes(), getType(), isInteger(), isNull()... - EXPECT_EQ(SQLITE_INTEGER, query.getColumn(0).getType()); - EXPECT_EQ(true, query.getColumn(0).isInteger()); - EXPECT_EQ(false, query.getColumn(0).isFloat()); - EXPECT_EQ(false, query.getColumn(0).isText()); - EXPECT_EQ(false, query.getColumn(0).isBlob()); - EXPECT_EQ(false, query.getColumn(0).isNull()); - EXPECT_EQ(1, query.getColumn(0).getBytes()); // size of the string "1" without the null terminator - EXPECT_EQ(SQLITE_TEXT, query.getColumn(1).getType()); - EXPECT_EQ(false, query.getColumn(1).isInteger()); - EXPECT_EQ(false, query.getColumn(1).isFloat()); - EXPECT_EQ(true, query.getColumn(1).isText()); - EXPECT_EQ(false, query.getColumn(1).isBlob()); - EXPECT_EQ(false, query.getColumn(1).isNull()); - EXPECT_EQ(5, query.getColumn(1).getBytes()); // size of the string "first" - EXPECT_EQ(SQLITE_INTEGER, query.getColumn(2).getType()); - EXPECT_EQ(true, query.getColumn(2).isInteger()); - EXPECT_EQ(false, query.getColumn(2).isFloat()); - EXPECT_EQ(false, query.getColumn(2).isText()); - EXPECT_EQ(false, query.getColumn(2).isBlob()); - EXPECT_EQ(false, query.getColumn(2).isNull()); - EXPECT_EQ(3, query.getColumn(2).getBytes()); // size of the string "123" - EXPECT_EQ(SQLITE_FLOAT, query.getColumn(3).getType()); - EXPECT_EQ(false, query.getColumn(3).isInteger()); - EXPECT_EQ(true, query.getColumn(3).isFloat()); - EXPECT_EQ(false, query.getColumn(3).isText()); - EXPECT_EQ(false, query.getColumn(3).isBlob()); - EXPECT_EQ(false, query.getColumn(3).isNull()); - EXPECT_EQ(5, query.getColumn(3).getBytes()); // size of the string "0.123" - EXPECT_EQ(5, query.getColumn(4).getBytes()); // size of the string "blob" with the null terminator - EXPECT_EQ(SQLITE_BLOB, query.getColumn(4).getType()); - EXPECT_EQ(false, query.getColumn(4).isInteger()); - EXPECT_EQ(false, query.getColumn(4).isFloat()); - EXPECT_EQ(false, query.getColumn(4).isText()); - EXPECT_EQ(true, query.getColumn(4).isBlob()); - EXPECT_EQ(false, query.getColumn(4).isNull()); - EXPECT_EQ(0, query.getColumn(5).getBytes()); // size of the string "" without the null terminator - EXPECT_EQ(SQLITE_NULL, query.getColumn(5).getType()); - EXPECT_EQ(false, query.getColumn(5).isInteger()); - EXPECT_EQ(false, query.getColumn(5).isFloat()); - EXPECT_EQ(false, query.getColumn(5).isText()); - EXPECT_EQ(false, query.getColumn(5).isBlob()); - EXPECT_EQ(true, query.getColumn(5).isNull()); - - } // Close DB test.db3 - remove("test.db3"); + // Validate getBytes(), getType(), isInteger(), isNull()... + EXPECT_EQ(SQLITE_INTEGER, query.getColumn(0).getType()); + EXPECT_EQ(true, query.getColumn(0).isInteger()); + EXPECT_EQ(false, query.getColumn(0).isFloat()); + EXPECT_EQ(false, query.getColumn(0).isText()); + EXPECT_EQ(false, query.getColumn(0).isBlob()); + EXPECT_EQ(false, query.getColumn(0).isNull()); + EXPECT_STREQ("1", query.getColumn(0).getText()); // convert to string + EXPECT_EQ(1, query.getColumn(0).getBytes()); // size of the string "1" without the null terminator + EXPECT_EQ(SQLITE_TEXT, query.getColumn(1).getType()); + EXPECT_EQ(false, query.getColumn(1).isInteger()); + EXPECT_EQ(false, query.getColumn(1).isFloat()); + EXPECT_EQ(true, query.getColumn(1).isText()); + EXPECT_EQ(false, query.getColumn(1).isBlob()); + EXPECT_EQ(false, query.getColumn(1).isNull()); + EXPECT_STREQ("first", query.getColumn(1).getText()); // convert to string + EXPECT_EQ(5, query.getColumn(1).getBytes()); // size of the string "first" + EXPECT_EQ(SQLITE_INTEGER, query.getColumn(2).getType()); + EXPECT_EQ(true, query.getColumn(2).isInteger()); + EXPECT_EQ(false, query.getColumn(2).isFloat()); + EXPECT_EQ(false, query.getColumn(2).isText()); + EXPECT_EQ(false, query.getColumn(2).isBlob()); + EXPECT_EQ(false, query.getColumn(2).isNull()); + EXPECT_STREQ("123", query.getColumn(2).getText()); // convert to string + EXPECT_EQ(3, query.getColumn(2).getBytes()); // size of the string "123" + EXPECT_EQ(SQLITE_FLOAT, query.getColumn(3).getType()); + EXPECT_EQ(false, query.getColumn(3).isInteger()); + EXPECT_EQ(true, query.getColumn(3).isFloat()); + EXPECT_EQ(false, query.getColumn(3).isText()); + EXPECT_EQ(false, query.getColumn(3).isBlob()); + EXPECT_EQ(false, query.getColumn(3).isNull()); + EXPECT_STREQ("0.123", query.getColumn(3).getText()); // convert to string + EXPECT_EQ(5, query.getColumn(3).getBytes()); // size of the string "0.123" + EXPECT_EQ(SQLITE_BLOB, query.getColumn(4).getType()); + EXPECT_EQ(false, query.getColumn(4).isInteger()); + EXPECT_EQ(false, query.getColumn(4).isFloat()); + EXPECT_EQ(false, query.getColumn(4).isText()); + EXPECT_EQ(true, query.getColumn(4).isBlob()); + EXPECT_EQ(false, query.getColumn(4).isNull()); + EXPECT_STREQ("blob", query.getColumn(4).getText()); // convert to string + EXPECT_EQ(5, query.getColumn(4).getBytes()); // size of the string "blob" WITH the null terminator (blob) + EXPECT_EQ(SQLITE_NULL, query.getColumn(5).getType()); + EXPECT_EQ(false, query.getColumn(5).isInteger()); + EXPECT_EQ(false, query.getColumn(5).isFloat()); + EXPECT_EQ(false, query.getColumn(5).isText()); + EXPECT_EQ(false, query.getColumn(5).isBlob()); + EXPECT_EQ(true, query.getColumn(5).isNull()); + EXPECT_STREQ("", query.getColumn(5).getText()); // convert to string + EXPECT_EQ(0, query.getColumn(5).getBytes()); // size of the string "" without the null terminator } + +TEST(Column, getName) { + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT)")); + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); + + // Compile a SQL query, using the "id" column name as-is, but aliasing the "msg" column with new name "value" + SQLite::Statement query(db, "SELECT id, msg as value FROM test"); + query.executeStep(); + + // Show how to get the aliased names of the result columns. + const std::string name0 = query.getColumn(0).getName(); + const std::string name1 = query.getColumn(1).getName(); + EXPECT_EQ("id", name0); + EXPECT_EQ("value", name1); + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + // Show how to get origin names of the table columns from which theses result columns come from. + // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be + // also defined at compile times of the SQLite library itself. + const std::string oname0 = query.getColumn(0).getOriginName(); + const std::string oname1 = query.getColumn(1).getOriginName(); + EXPECT_EQ("id", oname0); + EXPECT_EQ("msg", oname1); +#endif +} \ No newline at end of file From 7e16e8545f646a61919a51c8c13171fd9e3b3409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Fri, 1 May 2015 22:46:51 +0200 Subject: [PATCH 18/33] Re-enable SQLITE_ENABLE_COLUMN_METADATA by default under Windows --- CMakeLists.txt | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9d1817..97ea398 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,17 @@ project(SQLiteCpp) if (WIN32) set(DEV_NULL "NUL") # build the SQLite3 C library for Windows (for ease of use) - set(SQLITECPP_INTERNAL_SQLITE_DEFAULT ON) - set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT OFF) -else (WIN32) + set(SQLITECPP_INTERNAL_SQLITE_DEFAULT ON) + set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON) +else (WIN32) # UNIX set(DEV_NULL "/dev/null") # do not build the SQLite3 C library, but uses the Linux/Mac OS X sqlite3-dev package - set(SQLITECPP_INTERNAL_SQLITE_DEFAULT OFF) - set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON) + set(SQLITECPP_INTERNAL_SQLITE_DEFAULT OFF) + if (APPLE) + set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT OFF) + else (APPLE) + set(SQLITE_ENABLE_COLUMN_METADATA_DEFAULT ON) + endif (APPLE) endif (WIN32) # then Compiler/IDE differences: @@ -243,10 +247,10 @@ else (NOT BIICODE) # Link target with dl for linux if (UNIX) - target_link_libraries(${BII_BLOCK_TARGET} INTERFACE pthread) - if (NOT APPLE) - target_link_libraries(${BII_BLOCK_TARGET} INTERFACE dl) - endif () + target_link_libraries(${BII_BLOCK_TARGET} INTERFACE pthread) + if (NOT APPLE) + target_link_libraries(${BII_BLOCK_TARGET} INTERFACE dl) + endif () endif () endif (NOT BIICODE) From 078365febcc3171650e2beb8f68d6add04208778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 17:05:28 +0200 Subject: [PATCH 19/33] Fix part of issue #34 about 64 bits long with GCC on AMD64 --- include/SQLiteCpp/Column.h | 10 ++++++++++ tests/Column_test.cpp | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index d16e6f7..0364d4f 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -199,6 +199,16 @@ public: return getText(); } #endif + // NOTE : the following is required by GCC and Clang to cast a Column result in a long/int64_t + /// @brief Inline cast operator to long as 64bits integer + inline operator long() const + { +#ifdef __x86_64__ + return getInt64(); +#else + return getInt(); +#endif + } /// @brief Return UTF-8 encoded English language explanation of the most recent error. inline const char* errmsg() const diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index 8e52781..030449c 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include TEST(Column, basis) { @@ -54,7 +54,9 @@ TEST(Column, basis) { // validates every variant of cast operators, and conversions of types { - int64_t id = query.getColumn(0); // operator sqlite3_int64() + sqlite3_int64 id = query.getColumn(0); // operator sqlite3_int64() + int64_t id2 = query.getColumn(0); // operator sqlite3_int64() (or long() with GCC 64bits) + long id3 = query.getColumn(0); // operator sqlite3_int64() (or long() with GCC 64bits) const char* ptxt = query.getColumn(1); // operator const char*() const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) const int integer = query.getColumn(2); // operator int() @@ -62,6 +64,8 @@ TEST(Column, basis) { const void* pblob = query.getColumn(4); // operator void*() const void* pempty = query.getColumn(5); // operator void*() EXPECT_EQ(1, id); + EXPECT_EQ(1, id2); + EXPECT_EQ(1, id3); EXPECT_STREQ("first", ptxt); EXPECT_EQ("first", msg); EXPECT_EQ(123, integer); From 8797f16d12de8949a2e3199f7a45242d7ec3e5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 17:21:04 +0200 Subject: [PATCH 20/33] Add Statement::getColumnName(aIndex) - enable getting names befor gettings rows of result --- examples/example1/main.cpp | 32 +++--- include/SQLiteCpp/Statement.h | 62 ++++++++++- src/Statement.cpp | 132 ++++++++++------------ tests/Statement_test.cpp | 200 ++++++++++++++++++---------------- 4 files changed, 236 insertions(+), 190 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index 92cb7d2..a8296c1 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -140,28 +140,22 @@ int main () query.reset(); std::cout << "SQLite statement '" << query.getQuery().c_str() << "' reseted (" << query.getColumnCount() << " columns in the result)\n"; - // Execute the first step of the query, to get the fist row of results, and name of columns - if (query.executeStep()) - { - // Show how to get the aliased names of the result columns. - const std::string name0 = query.getColumn(0).getName(); - const std::string name1 = query.getColumn(1).getName(); - const std::string name2 = query.getColumn(2).getName(); - std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; + // Show how to get the aliased names of the result columns. + const std::string name0 = query.getColumnName(0); + const std::string name1 = query.getColumnName(1); + const std::string name2 = query.getColumnName(2); + std::cout << "aliased result [\"" << name0.c_str() << "\", \"" << name1.c_str() << "\", \"" << name2.c_str() << "\"]\n"; #ifdef SQLITE_ENABLE_COLUMN_METADATA - // Show how to get origin names of the table columns from which theses result columns come from. - // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be - // also defined at compile times of the SQLite library itself. - const std::string oname0 = query.getColumn(0).getOriginName(); - const std::string oname1 = query.getColumn(1).getOriginName(); - const std::string oname2 = query.getColumn(2).getOriginName(); - std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n"; + // Show how to get origin names of the table columns from which theses result columns come from. + // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be + // also defined at compile times of the SQLite library itself. + const std::string oname0 = query.getColumnOriginName(0); + const std::string oname1 = query.getColumnOriginName(1); + const std::string oname2 = query.getColumnOriginName(2); + std::cout << "origin table 'test' [\"" << oname0.c_str() << "\", \"" << oname1.c_str() << "\", \"" << oname2.c_str() << "\"]\n"; #endif - // Demonstrates that inserting column value in a std:ostream is natural - std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\", " << query.getColumn(2) << ")\n"; - } - // Loop to execute the rest of the query step by step, to get one a row of results at a time + // Loop to execute the query step by step, to get one a row of results at a time while (query.executeStep()) { // Demonstrates that inserting column value in a std:ostream is natural diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 5b620b3..e595e41 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -297,7 +297,7 @@ public: Column getColumn(const int aIndex); /** - * @brief Return a copy of the column data specified by its column name + * @brief Return a copy of the column data specified by its column name (less efficient than using an index) * * Can be used to access the data of the current row of result when applicable, * while the executeStep() method returns true. @@ -308,9 +308,11 @@ public: * - after the last executeStep() returned false * - after a reset() call * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. + * Throw an exception if the specified name is not an on of the aliased name of the columns in the result. * - * @param[in] apName Name of the column, starting at index 0 + * @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name) + * + * @note Uses a map of column names to indexes, build on first call. * * @note This method is not const, reflecting the fact that the returned Column object will * share the ownership of the underlying sqlite3_stmt. @@ -329,9 +331,33 @@ public: * @param[in] aIndex Index of the column, starting at 0 * * @return true if the column value is NULL + * + * Throw an exception if the specified index is out of the [0, getColumnCount()) range. */ bool isColumnNull(const int aIndex) const; + /** + * @brief Return a pointer to the named assigned to the specified result column (potentially aliased) + * + * @see getColumnOriginName() to get original column name (not aliased) + * + * Throw an exception if the specified index is out of the [0, getColumnCount()) range. + */ + const char* getColumnName(const int aIndex) const; + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + /** + * @brief Return a pointer to the table column name that is the origin of the specified result column + * + * Require definition of the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro : + * - when building the SQLite library itself (which is the case for the Debian libsqlite3 binary for instance), + * - and also when compiling this wrapper. + * + * Throw an exception if the specified index is out of the [0, getColumnCount()) range. + */ + const char* getColumnOriginName(const int aIndex) const; +#endif + //////////////////////////////////////////////////////////////////////////// /// @brief Return the UTF-8 SQL Query. @@ -423,7 +449,35 @@ private: * * @param[in] SQLite return code to test against the SQLITE_OK expected value */ - void check(const int aRet); + inline void Statement::check(const int aRet) const + { + if (SQLITE_OK != aRet) + { + throw SQLite::Exception(sqlite3_errmsg(mStmtPtr)); + } + } + + /** + * @brief Check if there is a row of result returnes by executeStep(), else throw a SQLite::Exception. + */ + inline void Statement::checkRow() const + { + if (false == mbOk) + { + throw SQLite::Exception("No row to get a column from. executeStep() was not called or did not returned true."); + } + } + + /** + * @brief Check if there is a Column index is in the range of columns in the result. + */ + inline void Statement::checkIndex(const int aIndex) const + { + if ((aIndex < 0) || (aIndex >= mColumnCount)) + { + throw SQLite::Exception("Column index out of range."); + } + } private: typedef std::map TColumnNames; diff --git a/src/Statement.cpp b/src/Statement.cpp index 597fcae..e219938 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -54,7 +54,7 @@ void Statement::reset() { mbOk = false; mbDone = false; - int ret = sqlite3_reset(mStmtPtr); + const int ret = sqlite3_reset(mStmtPtr); check(ret); } @@ -65,56 +65,56 @@ void Statement::clearBindings(void) { mbOk = false; mbDone = false; - int ret = sqlite3_clear_bindings(mStmtPtr); + const int ret = sqlite3_clear_bindings(mStmtPtr); check(ret); } // Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const int& aValue) { - int ret = sqlite3_bind_int(mStmtPtr, aIndex, aValue); + const int ret = sqlite3_bind_int(mStmtPtr, 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 sqlite3_int64& aValue) { - int ret = sqlite3_bind_int64(mStmtPtr, aIndex, aValue); + const int ret = sqlite3_bind_int64(mStmtPtr, 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) { - int ret = sqlite3_bind_double(mStmtPtr, aIndex, aValue); + const int ret = sqlite3_bind_double(mStmtPtr, 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) { - int ret = sqlite3_bind_text(mStmtPtr, aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(mStmtPtr, 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) { - int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, -1, SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(mStmtPtr, 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) { - int ret = sqlite3_bind_blob(mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT); + const int ret = sqlite3_bind_blob(mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT); 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) { - int ret = sqlite3_bind_null(mStmtPtr, aIndex); + const int ret = sqlite3_bind_null(mStmtPtr, aIndex); check(ret); } @@ -122,56 +122,56 @@ void Statement::bind(const int aIndex) // Bind an int value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const char* apName, const int& aValue) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_int(mStmtPtr, index, aValue); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_int(mStmtPtr, index, 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 char* apName, const sqlite3_int64& aValue) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_int64(mStmtPtr, index, aValue); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_int64(mStmtPtr, index, 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 char* apName, const double& aValue) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_double(mStmtPtr, index, aValue); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_double(mStmtPtr, index, aValue); check(ret); } // Bind a string value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const char* apName, const std::string& aValue) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_text(mStmtPtr, index, 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 char* apName, const char* apValue) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_text(mStmtPtr, index, apValue, -1, SQLITE_TRANSIENT); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_text(mStmtPtr, index, 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 char* apName, const void* apValue, const int aSize) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_blob(mStmtPtr, index, apValue, aSize, SQLITE_TRANSIENT); check(ret); } // Bind a NULL value to a parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const char* apName) { - int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - int ret = sqlite3_bind_null(mStmtPtr, index); + const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); + const int ret = sqlite3_bind_null(mStmtPtr, index); check(ret); } @@ -181,7 +181,7 @@ bool Statement::executeStep() { if (false == mbDone) { - int ret = sqlite3_step(mStmtPtr); + const int ret = sqlite3_step(mStmtPtr); if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it { mbOk = true; @@ -200,7 +200,7 @@ bool Statement::executeStep() } else { - throw SQLite::Exception("Statement needs to be reset"); + throw SQLite::Exception("Statement needs to be reseted."); } return mbOk; // true only if one row is accessible by getColumn(N) @@ -211,7 +211,7 @@ int Statement::exec() { if (false == mbDone) { - int ret = sqlite3_step(mStmtPtr); + const int ret = sqlite3_step(mStmtPtr); if (SQLITE_DONE == ret) // the statement has finished executing successfully { mbOk = false; @@ -221,7 +221,7 @@ int Statement::exec() { mbOk = false; mbDone = false; - throw SQLite::Exception("exec() does not expect results"); + throw SQLite::Exception("exec() does not expect results. Use executeStep."); } else { @@ -232,7 +232,7 @@ int Statement::exec() } else { - throw SQLite::Exception("Statement need to be reseted"); + throw SQLite::Exception("Statement need to be reseted."); } // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE) @@ -243,14 +243,8 @@ int Statement::exec() // (use the Column copy-constructor) Column Statement::getColumn(const int aIndex) { - if (false == mbOk) - { - throw SQLite::Exception("No row to get a column from"); - } - else if ((aIndex < 0) || (aIndex >= mColumnCount)) - { - throw SQLite::Exception("Column index out of range"); - } + checkRow(); + checkIndex(aIndex); // Share the Statement Object handle with the new Column created return Column(mStmtPtr, aIndex); @@ -260,62 +254,50 @@ Column Statement::getColumn(const int aIndex) // (use the Column copy-constructor) Column Statement::getColumn(const char* apName) { - int Index = -1; + checkRow(); - if (false == mbOk) + if (mColumnNames.empty()) { - throw SQLite::Exception("No row to get a column from"); + for (int i = 0; i < mColumnCount; ++i) + { + const char* pName = sqlite3_column_name(mStmtPtr, i); + mColumnNames[pName] = i; + } } - else - { - if (mColumnNames.empty()) - { - for (int i = 0; i < mColumnCount; ++i) - { - const char* pName = sqlite3_column_name(mStmtPtr, i); - mColumnNames[pName] = i; - } - } - const TColumnNames::const_iterator iIndex = mColumnNames.find(apName); - if (iIndex != mColumnNames.end()) - { - Index = (*iIndex).second; - } - else - { - throw SQLite::Exception("Column index out of range"); - } + const TColumnNames::const_iterator iIndex = mColumnNames.find(apName); + if (iIndex == mColumnNames.end()) + { + throw SQLite::Exception("Unknown column name."); } // Share the Statement Object handle with the new Column created - return Column(mStmtPtr, Index); + return Column(mStmtPtr, (*iIndex).second); } // Test if the column is NULL bool Statement::isColumnNull(const int aIndex) const { - if (false == mbOk) - { - throw SQLite::Exception("No row to get a column from"); - } - else if ((aIndex < 0) || (aIndex >= mColumnCount)) - { - throw SQLite::Exception("Column index out of range"); - } - + checkRow(); + checkIndex(aIndex); return (SQLITE_NULL == sqlite3_column_type(mStmtPtr, aIndex)); } -// Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message -void Statement::check(const int aRet) +// Return the named assigned to the specified result column (potentially aliased) +const char* Statement::getColumnName(const int aIndex) const { - if (SQLITE_OK != aRet) - { - throw SQLite::Exception(sqlite3_errmsg(mStmtPtr)); - } + checkIndex(aIndex); + return sqlite3_column_name(mStmtPtr, 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 +{ + checkIndex(aIndex); + return sqlite3_column_origin_name(mStmtPtr, aIndex); +} +#endif //////////////////////////////////////////////////////////////////////////////// // Internal class : shared pointer to the sqlite3_stmt SQLite Statement Object @@ -332,7 +314,7 @@ Statement::Ptr::Ptr(sqlite3* apSQLite, std::string& aQuery) : mpStmt(NULL), mpRefCount(NULL) { - int ret = sqlite3_prepare_v2(apSQLite, aQuery.c_str(), static_cast(aQuery.size()), &mpStmt, NULL); + const int ret = sqlite3_prepare_v2(apSQLite, aQuery.c_str(), static_cast(aQuery.size()), &mpStmt, NULL); if (SQLITE_OK != ret) { throw SQLite::Exception(sqlite3_errmsg(mpSQLite)); diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 19fd48b..e725a8d 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -18,115 +18,131 @@ TEST(Statement, invalid) { - remove("test.db3"); - { - // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // Compile a SQL query, but without any table in the database - EXPECT_THROW(SQLite::Statement query(db, "SELECT * FROM test"), SQLite::Exception); - EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); - EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); + // Compile a SQL query, but without any table in the database + EXPECT_THROW(SQLite::Statement query(db, "SELECT * FROM test"), SQLite::Exception); + EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); + EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // Compile a SQL query with no parameter - SQLite::Statement query(db, "SELECT * FROM test"); - EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); - EXPECT_EQ(2, query.getColumnCount ()); - EXPECT_FALSE(query.isOk()); - EXPECT_FALSE(query.isDone()); - EXPECT_THROW(query.isColumnNull(-1), SQLite::Exception); - EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); - EXPECT_THROW(query.isColumnNull(1), SQLite::Exception); - EXPECT_THROW(query.isColumnNull(2), SQLite::Exception); - EXPECT_THROW(query.getColumn(-1), SQLite::Exception); - EXPECT_THROW(query.getColumn(0), SQLite::Exception); - EXPECT_THROW(query.getColumn(1), SQLite::Exception); - EXPECT_THROW(query.getColumn(2), SQLite::Exception); + // Compile a SQL query with no parameter + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(2, query.getColumnCount ()); + EXPECT_FALSE(query.isOk()); + EXPECT_FALSE(query.isDone()); + EXPECT_THROW(query.isColumnNull(-1), SQLite::Exception); + EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); + EXPECT_THROW(query.isColumnNull(1), SQLite::Exception); + EXPECT_THROW(query.isColumnNull(2), SQLite::Exception); + EXPECT_THROW(query.getColumn(-1), SQLite::Exception); + EXPECT_THROW(query.getColumn(0), SQLite::Exception); + EXPECT_THROW(query.getColumn(1), SQLite::Exception); + EXPECT_THROW(query.getColumn(2), SQLite::Exception); - query.reset(); - EXPECT_FALSE(query.isOk()); - EXPECT_FALSE(query.isDone()); + query.reset(); + EXPECT_FALSE(query.isOk()); + EXPECT_FALSE(query.isDone()); - query.executeStep(); - EXPECT_FALSE(query.isOk()); - EXPECT_TRUE( query.isDone()); - query.reset(); - EXPECT_FALSE(query.isOk()); - EXPECT_FALSE(query.isDone()); + query.executeStep(); + EXPECT_FALSE(query.isOk()); + EXPECT_TRUE( query.isDone()); + query.reset(); + EXPECT_FALSE(query.isOk()); + EXPECT_FALSE(query.isDone()); - query.reset(); - EXPECT_THROW(query.bind(-1, 123), SQLite::Exception); - EXPECT_THROW(query.bind(0, 123), SQLite::Exception); - EXPECT_THROW(query.bind(1, 123), SQLite::Exception); - EXPECT_THROW(query.bind(2, 123), SQLite::Exception); - EXPECT_THROW(query.bind(0, "abc"), SQLite::Exception); - EXPECT_THROW(query.bind(0), SQLite::Exception); - EXPECT_EQ(SQLITE_RANGE, db.getErrorCode()); - EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode()); + query.reset(); + EXPECT_THROW(query.bind(-1, 123), SQLite::Exception); + EXPECT_THROW(query.bind(0, 123), SQLite::Exception); + EXPECT_THROW(query.bind(1, 123), SQLite::Exception); + EXPECT_THROW(query.bind(2, 123), SQLite::Exception); + EXPECT_THROW(query.bind(0, "abc"), SQLite::Exception); + EXPECT_THROW(query.bind(0), SQLite::Exception); + EXPECT_EQ(SQLITE_RANGE, db.getErrorCode()); + EXPECT_EQ(SQLITE_RANGE, db.getExtendedErrorCode()); - query.exec(); // exec() instead of executeStep() as there is no result - EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); - EXPECT_THROW(query.getColumn(0), SQLite::Exception); + query.exec(); // exec() instead of executeStep() as there is no result + EXPECT_THROW(query.isColumnNull(0), SQLite::Exception); + EXPECT_THROW(query.getColumn(0), SQLite::Exception); - // Add a first row - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); - EXPECT_EQ(1, db.getLastInsertRowid()); - EXPECT_EQ(1, db.getTotalChanges()); + // Add a first row + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); - query.reset(); - EXPECT_FALSE(query.isOk()); - EXPECT_FALSE(query.isDone()); + query.reset(); + EXPECT_FALSE(query.isOk()); + EXPECT_FALSE(query.isDone()); - EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result - - } // Close DB test.db3 - remove("test.db3"); + EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result } +// TODO: test every kind of binding TEST(Statement, getColumnByName) { - remove("test.db3"); - { - // Create a new database - SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // Create a new table - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)")); - EXPECT_EQ(SQLITE_OK, db.getErrorCode()); - EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); + // Create a new table + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT, int INTEGER, double REAL)")); + EXPECT_EQ(SQLITE_OK, db.getErrorCode()); + EXPECT_EQ(SQLITE_OK, db.getExtendedErrorCode()); - // Create a first row - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)")); - EXPECT_EQ(1, db.getLastInsertRowid()); - EXPECT_EQ(1, db.getTotalChanges()); + // Create a first row + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)")); + EXPECT_EQ(1, db.getLastInsertRowid()); + EXPECT_EQ(1, db.getTotalChanges()); - // Compile a SQL query - SQLite::Statement query(db, "SELECT * FROM test"); - EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); - EXPECT_EQ(4, query.getColumnCount()); - query.executeStep(); - EXPECT_TRUE (query.isOk()); - EXPECT_FALSE(query.isDone()); + // Compile a SQL query + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(4, query.getColumnCount()); + query.executeStep(); + EXPECT_TRUE (query.isOk()); + EXPECT_FALSE(query.isDone()); - // Look for unexisting columns - EXPECT_THROW(query.getColumn("unknown"), SQLite::Exception); - EXPECT_THROW(query.getColumn(""), SQLite::Exception); + // Look for unexisting columns + EXPECT_THROW(query.getColumn("unknown"), SQLite::Exception); + EXPECT_THROW(query.getColumn(""), SQLite::Exception); - const std::string msg = query.getColumn("msg"); - const int integer = query.getColumn("int"); - const double real = query.getColumn("double"); - EXPECT_EQ("first", msg); - EXPECT_EQ(123, integer); - EXPECT_EQ(0.123, real); - - } // Close DB test.db3 - remove("test.db3"); + const std::string msg = query.getColumn("msg"); + const int integer = query.getColumn("int"); + const double real = query.getColumn("double"); + EXPECT_EQ("first", msg); + EXPECT_EQ(123, integer); + EXPECT_EQ(0.123, real); +} + +TEST(Statement, getName) { + // Create a new database + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, msg TEXT)")); + + // Compile a SQL query, using the "id" column name as-is, but aliasing the "msg" column with new name "value" + SQLite::Statement query(db, "SELECT id, msg as value FROM test"); + query.executeStep(); + + const std::string name0 = query.getColumnName(0); + const std::string name1 = query.getColumnName(1); + EXPECT_EQ("id", name0); + EXPECT_EQ("value", name1); + +#ifdef SQLITE_ENABLE_COLUMN_METADATA + // Show how to get origin names of the table columns from which theses result columns come from. + // Requires the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro to be + // also defined at compile times of the SQLite library itself. + const std::string oname0 = query.getColumnOriginName(0); + const std::string oname1 = query.getColumnOriginName(1); + EXPECT_EQ("id", oname0); + EXPECT_EQ("msg", oname1); +#endif } From 31dbcda9ad7a1151f01e802a470683bb39be7571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 20:23:59 +0200 Subject: [PATCH 21/33] Minor fixes to comments and an API --- include/SQLiteCpp/Column.h | 2 +- include/SQLiteCpp/Database.h | 10 ++++++++-- src/Database.cpp | 12 +----------- src/Transaction.cpp | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 0364d4f..1679dbc 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -62,7 +62,7 @@ public: // they copy the Statement::Ptr which in turn increments the reference counter. /** - * @brief Return a pointer to the named assigned to a result column (potentially aliased) + * @brief Return a pointer to the named assigned to this result column (potentially aliased) * * @see getOriginName() to get original column name (not aliased) */ diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 39ae312..5eb5154 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -102,7 +102,7 @@ public: * * @throw SQLite::Exception in case of error */ - void setBusyTimeout(int aTimeoutMs) noexcept; // nothrow + void setBusyTimeout(const int aTimeoutMs) noexcept; // nothrow /** * @brief Shortcut to execute one or multiple statements without results. @@ -337,7 +337,13 @@ private: /** * @brief Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message */ - void check(const int aRet) const; + inline void Database::check(const int aRet) const + { + if (SQLITE_OK != aRet) + { + throw SQLite::Exception(sqlite3_errmsg(mpSQLite)); + } + } private: sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle diff --git a/src/Database.cpp b/src/Database.cpp index ba5e355..649d49e 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -74,7 +74,7 @@ Database::~Database() noexcept // nothrow * * @throw SQLite::Exception in case of error */ -void Database::setBusyTimeout(int aTimeoutMs) noexcept // nothrow +void Database::setBusyTimeout(const int aTimeoutMs) noexcept // nothrow { const int ret = sqlite3_busy_timeout(mpSQLite, aTimeoutMs); check(ret); @@ -113,15 +113,6 @@ bool Database::tableExists(const char* apTableName) return (1 == Nb); } -// Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message -void Database::check(const int aRet) const -{ - if (SQLITE_OK != aRet) - { - throw SQLite::Exception(sqlite3_errmsg(mpSQLite)); - } -} - // Attach a custom function to your sqlite database. Assumes UTF8 text representation. // Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html void Database::createFunction(const char* apFuncName, @@ -140,7 +131,6 @@ void Database::createFunction(const char* apFuncName, } const int ret = sqlite3_create_function_v2(mpSQLite, apFuncName, aNbArg, TextRep, apApp, apFunc, apStep, apFinal, apDestroy); - check(ret); } diff --git a/src/Transaction.cpp b/src/Transaction.cpp index 5971d97..d486f3b 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -53,7 +53,7 @@ void Transaction::commit() } else { - throw SQLite::Exception("Transaction already commited"); + throw SQLite::Exception("Transaction already commited."); } } From f5e0cafa721dd54d63f4c6a832420344d26eb3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 21:42:47 +0200 Subject: [PATCH 22/33] Minor fixes to comments and fix the build --- include/SQLiteCpp/Statement.h | 12 ++++++++---- src/Statement.cpp | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index e595e41..610b784 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -79,8 +79,12 @@ public: /** * @brief Clears away all the bindings of a prepared statement. + * + * Contrary to the intuition of many, reset() does not reset + * the bindings on a prepared statement. + * Use this routine to reset all parameters to NULL. */ - void clearBindings(void); // throw(SQLite::Exception) + void clearBindings(); // throw(SQLite::Exception) //////////////////////////////////////////////////////////////////////////// // Bind a value to a parameter of the SQL statement, @@ -449,7 +453,7 @@ private: * * @param[in] SQLite return code to test against the SQLITE_OK expected value */ - inline void Statement::check(const int aRet) const + inline void check(const int aRet) const { if (SQLITE_OK != aRet) { @@ -460,7 +464,7 @@ private: /** * @brief Check if there is a row of result returnes by executeStep(), else throw a SQLite::Exception. */ - inline void Statement::checkRow() const + inline void checkRow() const { if (false == mbOk) { @@ -471,7 +475,7 @@ private: /** * @brief Check if there is a Column index is in the range of columns in the result. */ - inline void Statement::checkIndex(const int aIndex) const + inline void checkIndex(const int aIndex) const { if ((aIndex < 0) || (aIndex >= mColumnCount)) { diff --git a/src/Statement.cpp b/src/Statement.cpp index e219938..226e526 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -59,9 +59,7 @@ void Statement::reset() } // Clears away all the bindings of a prepared statement. -// "Contrary to the intuition of many, [sqlite3_reset()] does not reset -// ** the [sqlite3_bind_blob | bindings] on a [prepared statement]." -void Statement::clearBindings(void) +void Statement::clearBindings() { mbOk = false; mbDone = false; From b9322fb0abfb8828e5028824ec4b11c642373824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 21:43:10 +0200 Subject: [PATCH 23/33] Example now print SQLite version --- examples/example1/main.cpp | 3 +++ include/SQLiteCpp/Database.h | 2 +- tests/Database_test.cpp | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index a8296c1..494de42 100644 --- a/examples/example1/main.cpp +++ b/examples/example1/main.cpp @@ -83,6 +83,9 @@ private: int main () { + std::cout << "SQlite3 version " << SQLITE_VERSION << std::endl; + std::cout << "SQliteC++ version " << SQLITECPP_VERSION << std::endl; + //////////////////////////////////////////////////////////////////////////// // Very basic first example (1/7) : try diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 5eb5154..65a2c96 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -337,7 +337,7 @@ private: /** * @brief Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message */ - inline void Database::check(const int aRet) const + inline void check(const int aRet) const { if (SQLITE_OK != aRet) { diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index a693e3b..5414ef1 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -21,7 +21,7 @@ namespace SQLite /// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) { - // TODO test that this assertion callback get called + // TODO: test that this assertion callback get called std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n"; } } @@ -215,7 +215,7 @@ TEST(Database, execException) { // exception with SQL error: "No row to get a column from" EXPECT_THROW(db.execAndGet("SELECT weight FROM test WHERE value=\"second\""), SQLite::Exception); - // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" + // Add a row with more values than columns in the table: "table test has 3 columns but 4 values were supplied" EXPECT_THROW(db.exec("INSERT INTO test VALUES (NULL, \"first\", 123, 0.123)"), SQLite::Exception); EXPECT_EQ(SQLITE_ERROR, db.getErrorCode()); EXPECT_EQ(SQLITE_ERROR, db.getExtendedErrorCode()); From 3c39f1ff1c6a94a61b5e434f251f372dfe84608b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 22:16:47 +0200 Subject: [PATCH 24/33] Fix 3 new cpplint warnings about max line size --- include/SQLiteCpp/Statement.h | 2 +- src/Statement.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 610b784..231c8e0 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -468,7 +468,7 @@ private: { if (false == mbOk) { - throw SQLite::Exception("No row to get a column from. executeStep() was not called or did not returned true."); + throw SQLite::Exception("No row to get a column from. executeStep() was not called, or returned false."); } } diff --git a/src/Statement.cpp b/src/Statement.cpp index 226e526..86e5919 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -91,7 +91,8 @@ void Statement::bind(const int aIndex, const double& aValue) // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const std::string& aValue) { - const int ret = sqlite3_bind_text(mStmtPtr, aIndex, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(mStmtPtr, aIndex, aValue.c_str(), + static_cast(aValue.size()), SQLITE_TRANSIENT); check(ret); } @@ -145,7 +146,8 @@ void Statement::bind(const char* apName, const double& aValue) void Statement::bind(const char* apName, const std::string& aValue) { const int index = sqlite3_bind_parameter_index(mStmtPtr, apName); - const int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(), static_cast(aValue.size()), SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(mStmtPtr, index, aValue.c_str(), + static_cast(aValue.size()), SQLITE_TRANSIENT); check(ret); } From e537195625bda6af177f36b7a3db5b3bff9bdafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 22:24:34 +0200 Subject: [PATCH 25/33] Fix #47 setBusyTimeout in constructor - add corresponding Unit Test --- include/SQLiteCpp/Database.h | 12 ++++++++++-- src/Database.cpp | 20 ++++++++++++++++++-- tests/Database_test.cpp | 36 ++++++++++++++++++++++++------------ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 65a2c96..190446b 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -55,11 +55,15 @@ public: * * @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... + * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default * * @throw SQLite::Exception in case of error */ - Database(const char* apFilename, const int aFlags = SQLITE_OPEN_READONLY, const char* apVfs = NULL); + Database(const char* apFilename, + const int aFlags = SQLITE_OPEN_READONLY, + const int aTimeoutMs = 0, + const char* apVfs = NULL); /** * @brief Open the provided database UTF-8 filename. @@ -73,11 +77,15 @@ public: * * @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... + * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default * * @throw SQLite::Exception in case of error */ - Database(const std::string& aFilename, const int aFlags = SQLITE_OPEN_READONLY, const std::string& aVfs = ""); + Database(const std::string& aFilename, + const int aFlags = SQLITE_OPEN_READONLY, + const int aTimeoutMs = 0, + const std::string& aVfs = ""); /** * @brief Close the SQLite database connection. diff --git a/src/Database.cpp b/src/Database.cpp index 649d49e..bf94675 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -26,7 +26,10 @@ namespace SQLite // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. -Database::Database(const char* apFilename, const int aFlags /*= SQLITE_OPEN_READONLY*/, const char* apVfs /*= NULL*/) : +Database::Database(const char* apFilename, + const int aFlags /* = SQLITE_OPEN_READONLY*/, + const int aTimeoutMs /* = 0 */, + const char* apVfs /* = NULL*/) : mpSQLite(NULL), mFilename(apFilename) { @@ -37,10 +40,18 @@ Database::Database(const char* apFilename, const int aFlags /*= SQLITE_OPEN_READ sqlite3_close(mpSQLite); // close is required even in case of error on opening throw SQLite::Exception(strerr); } + + if (aTimeoutMs > 0) + { + setBusyTimeout(aTimeoutMs); + } } // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. -Database::Database(const std::string& aFilename, const int aFlags /*= SQLITE_OPEN_READONLY*/, const std::string& aVfs) : +Database::Database(const std::string& aFilename, + const int aFlags /* = SQLITE_OPEN_READONLY*/, + const int aTimeoutMs /* = 0 */, + const std::string& aVfs /* = "" */) : mpSQLite(NULL), mFilename(aFilename) { @@ -51,6 +62,11 @@ Database::Database(const std::string& aFilename, const int aFlags /*= SQLITE_OPE sqlite3_close(mpSQLite); // close is required even in case of error on opening throw SQLite::Exception(strerr); } + + if (aTimeoutMs > 0) + { + setBusyTimeout(aTimeoutMs); + } } // Close the SQLite database connection. diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 5414ef1..57a8cd2 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -32,7 +32,8 @@ TEST(Database, ctorExecCreateDropExist) { remove("test.db3"); { // Try to open an unexisting database - EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception); + std::string filename = "test.db3"; + EXPECT_THROW(SQLite::Database not_found(filename), SQLite::Exception); // Create a new database SQLite::Database db("test.db3", SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); @@ -92,20 +93,31 @@ TEST(Database, inMemory) { } // Close an destroy DB } -#if SQLITE_VERSION_NUMBER >= 3007015 // first version with PRAGMA busy_timeout +#if SQLITE_VERSION_NUMBER >= 3007015 // SQLite v3.7.15 is first version with PRAGMA busy_timeout TEST(Database, busyTimeout) { - // Create a new database - SQLite::Database db(":memory:"); - // Busy timeout default to 0ms: any contention between threads or process leads to SQLITE_BUSY error - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + { + // Create a new database with default timeout of 0ms + SQLite::Database db(":memory:"); + // Busy timeout default to 0ms: any contention between threads or process leads to SQLITE_BUSY error + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); - // Set a non null busy timeout: any contention between threads will leads to as much retry as possible during the time - db.setBusyTimeout(5000); - EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + // Set a non null busy timeout: any contention between threads will leads to as much retry as possible during the time + db.setBusyTimeout(5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); - // Reset timeout to null - db.setBusyTimeout(0); - EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + // Reset timeout to 0 + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + } + { + // Create a new database with a non null busy timeout + SQLite::Database db(":memory:", SQLITE_OPEN_READWRITE, 5000); + EXPECT_EQ(5000, db.execAndGet("PRAGMA busy_timeout").getInt()); + + // Reset timeout to null + db.setBusyTimeout(0); + EXPECT_EQ(0, db.execAndGet("PRAGMA busy_timeout").getInt()); + } } #endif // SQLITE_VERSION_NUMBER >= 3007015 From 09db07ccc7450be0b525821f7525810c0505316d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 22:56:21 +0200 Subject: [PATCH 26/33] Updated version to 1.0.0 changelog and copyright date --- CHANGELOG.txt | 15 ++++++++++++++- TODO.txt | 17 ++++++++--------- examples/example1/main.cpp | 2 +- include/SQLiteCpp/Column.h | 2 +- include/SQLiteCpp/Database.h | 32 ++++++++++++++++---------------- include/SQLiteCpp/SQLiteCpp.h | 6 +++--- sqlite3/CMakeLists.txt | 2 +- src/Column.cpp | 2 +- src/Database.cpp | 26 +++++++++++++------------- src/Transaction.cpp | 1 + tests/Database_test.cpp | 2 +- tests/Statement_test.cpp | 2 +- 12 files changed, 61 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index fc69057..1f4ede4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -41,6 +41,19 @@ Version 0.7.0 - January 9 2014 Added std::string version of existing APIs Improved CMake with more build options and Doxygen auto-detection -Version 0.8.0 - Februrary 26 2014 +Version 0.8.0 - February 26 2014 + Database constructor support opening a database with a custom VFS (default to NULL) Changed Column::getText() to return empty string "" by default instead of NULL pointer (to handle std::string conversion) +Version 1.0.0 - pending May 2015 + Public headers file moved to include/ dir + Added support to biicode in CMakeLists.txt + Added Unit Tests + Added a aBusyTimeoutMs parameter to Database() constructors + Added a Database::getTotalChanges() + Added a Database::getErrorCode() + Added a Statement::clearBindings() + Added a Statement::getColumn(aName) + Added a Statement::getErrorCode() + Added a Statement::getColumnName(aIndex) + Added a Statement::getColumnOriginName(aIndex) diff --git a/TODO.txt b/TODO.txt index 0bbd02a..a1de34d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,24 +1,23 @@ -Add a full googletest suite - Add a Tutorial: for SQLite newbies -Create Github Wiki pages with the README.md and FAQ.txt: Installation, Examples, Tutorial, How to contribute - -Publish a versionned ZIP file in Google Project Mirror +Improve Github Wiki pages with the FAQ: Installation, Examples, Tutorial, How to contribute Publish the Doxygen Documentation in the Github Pages (gh-pages branch) -Missing features in v0.9.9: -- getColumnByName() (issue #23) ? std::map getRow() ? +Missing features in v1.0.0: - bind a SQLITE_STATIC value (string/blob) - bind a dynamic value with zerocopy (unlike SQLITE_TRANSIENT) with custom deleter -Missing documentation in v0.9.9: +Missing documentation in v1.0.0: - explain the noncopyable property for RAII design - comment on returning error code instead of exception that shall not be thrown when exepected (!?) +Missing unit tests in v1.0.0: +- Create Function +- Assert Handler +- Binding variants + Advanced missing features: - backup support to/from file/:memory: -- Function ? - Agregate ? - support for different transaction mode ? NO: too specific - operator<< binding ? NO: redundant with bind() diff --git a/examples/example1/main.cpp b/examples/example1/main.cpp index 494de42..9a37f89 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-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-2015 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/Column.h b/include/SQLiteCpp/Column.h index 1679dbc..591419c 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement. * - * Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-2015 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/Database.h b/include/SQLiteCpp/Database.h index 190446b..8696577 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -53,17 +53,17 @@ public: * * Exception is thrown in case of error, then the Database object is NOT constructed. * - * @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) - * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... - * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) - * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default + * @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) + * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... + * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) + * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default * * @throw SQLite::Exception in case of error */ Database(const char* apFilename, - const int aFlags = SQLITE_OPEN_READONLY, - const int aTimeoutMs = 0, - const char* apVfs = NULL); + const int aFlags = SQLITE_OPEN_READONLY, + const int aBusyTimeoutMs = 0, + const char* apVfs = NULL); /** * @brief Open the provided database UTF-8 filename. @@ -75,17 +75,17 @@ public: * * Exception is thrown in case of error, then the Database object is NOT constructed. * - * @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) - * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... - * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) - * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default + * @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) + * @param[in] aFlags SQLITE_OPEN_READONLY/SQLITE_OPEN_READWRITE/SQLITE_OPEN_CREATE... + * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) + * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default * * @throw SQLite::Exception in case of error */ Database(const std::string& aFilename, - const int aFlags = SQLITE_OPEN_READONLY, - const int aTimeoutMs = 0, - const std::string& aVfs = ""); + const int aFlags = SQLITE_OPEN_READONLY, + const int aBusyTimeoutMs = 0, + const std::string& aVfs = ""); /** * @brief Close the SQLite database connection. @@ -106,11 +106,11 @@ public: * Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;". * Default busy timeout is 0ms. * - * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY + * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY * * @throw SQLite::Exception in case of error */ - void setBusyTimeout(const int aTimeoutMs) noexcept; // nothrow + void setBusyTimeout(const int aBusyTimeoutMs) noexcept; // nothrow /** * @brief Shortcut to execute one or multiple statements without results. diff --git a/include/SQLiteCpp/SQLiteCpp.h b/include/SQLiteCpp/SQLiteCpp.h index 6228001..a42f20d 100644 --- a/include/SQLiteCpp/SQLiteCpp.h +++ b/include/SQLiteCpp/SQLiteCpp.h @@ -5,7 +5,7 @@ * * Include this main header file in your project to gain access to all functionality provided by the wrapper. * - * Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-2015 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) @@ -38,5 +38,5 @@ * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same * numbers used in [SQLITECPP_VERSION]. */ -#define SQLITECPP_VERSION "0.9.9" -#define SQLITECPP_VERSION_NUMBER 0009009 +#define SQLITECPP_VERSION "1.0.0" +#define SQLITECPP_VERSION_NUMBER 1000000 diff --git a/sqlite3/CMakeLists.txt b/sqlite3/CMakeLists.txt index d163f50..7d615be 100644 --- a/sqlite3/CMakeLists.txt +++ b/sqlite3/CMakeLists.txt @@ -1,6 +1,6 @@ # CMake file for compiling the sqlite3 static library under Windows (for ease of use) # -# Copyright (c) 2013-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) +# Copyright (c) 2013-2015 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/Column.cpp b/src/Column.cpp index c0b7991..eb68e96 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement. * - * Copyright (c) 2012-2014 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * Copyright (c) 2012-2015 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 bf94675..178ecd7 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -27,9 +27,9 @@ namespace SQLite // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. Database::Database(const char* apFilename, - const int aFlags /* = SQLITE_OPEN_READONLY*/, - const int aTimeoutMs /* = 0 */, - const char* apVfs /* = NULL*/) : + const int aFlags /* = SQLITE_OPEN_READONLY*/, + const int aBusyTimeoutMs /* = 0 */, + const char* apVfs /* = NULL*/) : mpSQLite(NULL), mFilename(apFilename) { @@ -41,17 +41,17 @@ Database::Database(const char* apFilename, throw SQLite::Exception(strerr); } - if (aTimeoutMs > 0) + if (aBusyTimeoutMs > 0) { - setBusyTimeout(aTimeoutMs); + setBusyTimeout(aBusyTimeoutMs); } } // Open the provided database UTF-8 filename with SQLITE_OPEN_xxx provided flags. Database::Database(const std::string& aFilename, - const int aFlags /* = SQLITE_OPEN_READONLY*/, - const int aTimeoutMs /* = 0 */, - const std::string& aVfs /* = "" */) : + const int aFlags /* = SQLITE_OPEN_READONLY*/, + const int aBusyTimeoutMs /* = 0 */, + const std::string& aVfs /* = "" */) : mpSQLite(NULL), mFilename(aFilename) { @@ -63,9 +63,9 @@ Database::Database(const std::string& aFilename, throw SQLite::Exception(strerr); } - if (aTimeoutMs > 0) + if (aBusyTimeoutMs > 0) { - setBusyTimeout(aTimeoutMs); + setBusyTimeout(aBusyTimeoutMs); } } @@ -86,13 +86,13 @@ Database::~Database() noexcept // nothrow * Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;". * Default busy timeout is 0ms. * - * @param[in] aTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY + * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY * * @throw SQLite::Exception in case of error */ -void Database::setBusyTimeout(const int aTimeoutMs) noexcept // nothrow +void Database::setBusyTimeout(const int aBusyTimeoutMs) noexcept // nothrow { - const int ret = sqlite3_busy_timeout(mpSQLite, aTimeoutMs); + const int ret = sqlite3_busy_timeout(mpSQLite, aBusyTimeoutMs); check(ret); } diff --git a/src/Transaction.cpp b/src/Transaction.cpp index d486f3b..1b60aa5 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -38,6 +38,7 @@ Transaction::~Transaction() noexcept // nothrow catch (SQLite::Exception& e) { // Never throw an exception in a destructor + (void)e; // warning proof SQLITECPP_ASSERT(false, e.what()); // See SQLITECPP_ENABLE_ASSERT_HANDLER } } diff --git a/tests/Database_test.cpp b/tests/Database_test.cpp index 57a8cd2..7441fc9 100644 --- a/tests/Database_test.cpp +++ b/tests/Database_test.cpp @@ -21,7 +21,7 @@ namespace SQLite /// definition of the assertion handler enabled when SQLITECPP_ENABLE_ASSERT_HANDLER is defined in the project (CMakeList.txt) void assertion_failed(const char* apFile, const long apLine, const char* apFunc, const char* apExpr, const char* apMsg) { - // TODO: test that this assertion callback get called + // TODO: unit test that this assertion callback get called (already tested manually) std::cout << "assertion_failed(" << apFile << ", " << apLine << ", " << apFunc << ", " << apExpr << ", " << apMsg << ")\n"; } } diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index e725a8d..c76dcc5 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -84,7 +84,7 @@ TEST(Statement, invalid) { EXPECT_THROW(query.exec(), SQLite::Exception); // exec() shall throw as it does not expect a result } -// TODO: test every kind of binding +// TODO: test every kind of binding + clearBindings() TEST(Statement, getColumnByName) { // Create a new database From 4f4c9aa918f7f3ffb3a09b0461ba10d43b9c91b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Sun, 3 May 2015 23:07:49 +0200 Subject: [PATCH 27/33] Add a release badge to show 1.0.0 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4564b1b..b1bd492 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ SQLiteC++ --------- +[![release](https://img.shields.io/github/release/SRombauts/SQLiteCpp.svg?style=flat-square)](https://github.com/SRombauts/SQLiteCpp/releases) [![Travis CI Linux Build Status](https://travis-ci.org/SRombauts/SQLiteCpp.svg)](https://travis-ci.org/SRombauts/SQLiteCpp "Travis CI Linux Build Status") [![AppVeyor Windows Build status](https://ci.appveyor.com/api/projects/status/github/SRombauts/SQLiteCpp?svg=true)](https://ci.appveyor.com/project/SbastienRombauts/SQLiteCpp "AppVeyor Windows Build status") -[![Build Status](https://webapi.biicode.com/v1/badges/sqlite/sqlite/sqlite/master)](https://www.biicode.com/sqlite/sqlite) +[![Biicode Build Status](https://webapi.biicode.com/v1/badges/sqlite/sqlite/sqlite/master)](https://www.biicode.com/sqlite/sqlite) SQLiteC++ (SQLiteCpp) is a smart and easy to use C++ SQLite3 wrapper. From e55fffefd7b80d692c22693682725bb3de2af3a5 Mon Sep 17 00:00:00 2001 From: Onur Aslan Date: Wed, 6 May 2015 00:22:43 +0300 Subject: [PATCH 28/33] Use sqlite_errstr instead of sqlite3_errmsg mpSQLite is free'd with sqlite3_close. It's no more accesible. Fixes: #48 --- src/Database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database.cpp b/src/Database.cpp index 178ecd7..b11ff78 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -74,7 +74,7 @@ Database::~Database() noexcept // nothrow { const int ret = sqlite3_close(mpSQLite); // Never throw an exception in a destructor - SQLITECPP_ASSERT(SQLITE_OK == ret, sqlite3_errmsg(mpSQLite)); // See SQLITECPP_ENABLE_ASSERT_HANDLER + SQLITECPP_ASSERT(SQLITE_OK == ret, sqlite3_errstr(ret)); // See SQLITECPP_ENABLE_ASSERT_HANDLER } /** From 318f742b5ca3fd66c325ea0451419e0cb4929438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Wed, 6 May 2015 09:10:39 +0200 Subject: [PATCH 29/33] Revert use sqlite_errstr instead of sqlite3_errmsg that fixed #48 - sqlite3_errstr() is new from SQLite v3.7.15, not supported on Ubuntu 12.04 which is what is used for continuous integration with Travis CI - Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized) so use this generic string instead --- src/Database.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Database.cpp b/src/Database.cpp index b11ff78..6fe39bf 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -73,8 +73,9 @@ Database::Database(const std::string& aFilename, Database::~Database() noexcept // nothrow { const int ret = sqlite3_close(mpSQLite); - // Never throw an exception in a destructor - SQLITECPP_ASSERT(SQLITE_OK == ret, sqlite3_errstr(ret)); // See SQLITECPP_ENABLE_ASSERT_HANDLER + // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized) + // Never throw an exception in a destructor : + SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked"); // See SQLITECPP_ENABLE_ASSERT_HANDLER } /** From 368049a613198ac7009e018f6bfa681b60905590 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 8 May 2015 09:47:12 +0100 Subject: [PATCH 30/33] Added support for extension loading --- include/SQLiteCpp/Database.h | 19 +++++++++++++++++++ src/Database.cpp | 13 +++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 8696577..e31f545 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -336,6 +336,25 @@ public: apApp, apFunc, apStep, apFinal, apDestroy); } + + /** + * @brief Load a module into the current sqlite database instance. + * + * This is the equivalent of the sqlite3_load_extension call, but additionally enables + * module loading support prior to loading the requested module. + * + * @see http://www.sqlite.org/c3ref/load_extension.html + * + * @note UTF-8 text encoding assumed. + * + * @param[in] apExtensionName Name of the shared library containing extension + * @param[in] apEntryPointName Name of the entry point (NULL to let sqlite work it out) + * + * @throw SQLite::Exception in case of error + */ + void loadExtension(const char* apExtensionName, + const char *apEntryPointName); + private: /// @{ Database must be non-copyable Database(const Database&); diff --git a/src/Database.cpp b/src/Database.cpp index 6fe39bf..cd04584 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -151,5 +151,18 @@ void Database::createFunction(const char* apFuncName, check(ret); } +// Load an extension into the sqlite database. Only affects the current connection. +// Parameter details can be found here: http://www.sqlite.org/c3ref/load_extension.html +void Database::loadExtension(const char* apExtensionName, + const char *apEntryPointName) +{ + int ret = sqlite3_enable_load_extension(mpSQLite, 1); + + check(ret); + + ret = sqlite3_load_extension(mpSQLite, apExtensionName, apEntryPointName, 0); + + check(ret); +} } // namespace SQLite From 99db2f8bdba48745738afb29ece7b2cd84cdc0e4 Mon Sep 17 00:00:00 2001 From: ncr Date: Sat, 16 May 2015 17:42:45 +0300 Subject: [PATCH 31/33] Added conditional support for extension loading --- src/Database.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Database.cpp b/src/Database.cpp index cd04584..284b661 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -156,6 +156,12 @@ void Database::createFunction(const char* apFuncName, void Database::loadExtension(const char* apExtensionName, const char *apEntryPointName) { +#ifdef SQLITE_OMIT_LOAD_EXTENSION +# + throw std::runtime_error("sqlite extensions are disabled"); +# +#else +# int ret = sqlite3_enable_load_extension(mpSQLite, 1); check(ret); @@ -163,6 +169,8 @@ void Database::loadExtension(const char* apExtensionName, ret = sqlite3_load_extension(mpSQLite, apExtensionName, apEntryPointName, 0); check(ret); +# +#endif } } // namespace SQLite From e4b5d6eeed7c36c7a12de6813304b998d7d24493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Mon, 18 May 2015 21:10:48 +0200 Subject: [PATCH 32/33] Update version to 1.1.0 with bugfix and new loadExtension() --- CHANGELOG.txt | 26 +++++++++++++++----------- TODO.txt | 23 +++++++++++++++-------- include/SQLiteCpp/SQLiteCpp.h | 4 ++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1f4ede4..e62d082 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -34,10 +34,10 @@ Version 0.5.1 - April 7 2013 Version 0.6.0 - November 22 2013 Renamed Column::getName() to Column::getOriginName() - Added a new Column::getName() + Added Column::getName() Version 0.7.0 - January 9 2014 - Added a new Database::createFunction() API + Added Database::createFunction() Added std::string version of existing APIs Improved CMake with more build options and Doxygen auto-detection @@ -45,15 +45,19 @@ Version 0.8.0 - February 26 2014 Database constructor support opening a database with a custom VFS (default to NULL) Changed Column::getText() to return empty string "" by default instead of NULL pointer (to handle std::string conversion) -Version 1.0.0 - pending May 2015 +Version 1.0.0 - May 3 2015 Public headers file moved to include/ dir Added support to biicode in CMakeLists.txt Added Unit Tests - Added a aBusyTimeoutMs parameter to Database() constructors - Added a Database::getTotalChanges() - Added a Database::getErrorCode() - Added a Statement::clearBindings() - Added a Statement::getColumn(aName) - Added a Statement::getErrorCode() - Added a Statement::getColumnName(aIndex) - Added a Statement::getColumnOriginName(aIndex) + Added aBusyTimeoutMs parameter to Database() constructors + Added Database::getTotalChanges() + Added Database::getErrorCode() + Added Statement::clearBindings() + Added Statement::getColumn(aName) + Added Statement::getErrorCode() + Added Statement::getColumnName(aIndex) + Added Statement::getColumnOriginName(aIndex) + +Version 1.1.0 - May 2015 ? + Fix valgrind error on Database destructor + Added Database::loadExtension \ No newline at end of file diff --git a/TODO.txt b/TODO.txt index a1de34d..80640cd 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,27 +1,34 @@ -Add a Tutorial: for SQLite newbies -Improve Github Wiki pages with the FAQ: Installation, Examples, Tutorial, How to contribute +Add a Tutorial for SQLite newbies +Add a real example (rework current example?) +Improve Github Wiki pages with the FAQ: Installation, Examples, Tutorial, How to contribute Publish the Doxygen Documentation in the Github Pages (gh-pages branch) -Missing features in v1.0.0: +Missing features in v1.1.0: - bind a SQLITE_STATIC value (string/blob) - bind a dynamic value with zerocopy (unlike SQLITE_TRANSIENT) with custom deleter +- #24: executemany() like in Python https://docs.python.org/2/library/sqlite3.html#sqlite3.Connection.executemany +- #34: Better type for getColumn -Missing documentation in v1.0.0: +Missing documentation in v1.1.0: - explain the noncopyable property for RAII design -- comment on returning error code instead of exception that shall not be thrown when exepected (!?) +- comment on returning error code instead of exception that shall not be thrown when expected (!?) Missing unit tests in v1.0.0: +- Binding variants - Create Function - Assert Handler -- Binding variants +- Load Extension (not practicable, and easy to verify by code review) Advanced missing features: +- #39: SAVEPOINT https://www.sqlite.org/lang_savepoint.html + - backup support to/from file/:memory: +- Add optional usage of experimental sqlite3_trace() function to enable statistics - Agregate ? + - support for different transaction mode ? NO: too specific - operator<< binding ? NO: redundant with bind() - ATTACH Database ? NO: can already be done by "ATTACH" Statement -- Add optional usage of experimental sqlite3_trace() function to enable statistics -Post an article to CodeProject: Is there a license issue ? \ No newline at end of file +Post an article to CodeProject: Is there a license issue ? diff --git a/include/SQLiteCpp/SQLiteCpp.h b/include/SQLiteCpp/SQLiteCpp.h index a42f20d..7bb1dea 100644 --- a/include/SQLiteCpp/SQLiteCpp.h +++ b/include/SQLiteCpp/SQLiteCpp.h @@ -38,5 +38,5 @@ * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same * numbers used in [SQLITECPP_VERSION]. */ -#define SQLITECPP_VERSION "1.0.0" -#define SQLITECPP_VERSION_NUMBER 1000000 +#define SQLITECPP_VERSION "1.1.0" +#define SQLITECPP_VERSION_NUMBER 1001000 From 5a8a6e243edd32e8ee45a3d13b60aefcc44d0962 Mon Sep 17 00:00:00 2001 From: Aurelien ALBERT Date: Thu, 21 May 2015 10:52:46 +0200 Subject: [PATCH 33/33] Removed a warning in release build with Visual Studio --- src/Database.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Database.cpp b/src/Database.cpp index 284b661..b9440ac 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -73,6 +73,10 @@ Database::Database(const std::string& aFilename, Database::~Database() noexcept // nothrow { const int ret = sqlite3_close(mpSQLite); + + // Avoid unreferenced variable warning when build in release mode + (void) ret; + // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized) // Never throw an exception in a destructor : SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked"); // See SQLITECPP_ENABLE_ASSERT_HANDLER