diff --git a/CHANGELOG.md b/CHANGELOG.md index 16bdece6..f20a4e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Version 3.3.4 - TBD +- Added support for prepared statement flags (PREPARE_PERSISTENT, PREPARE_DONT_LOG, etc.) +- Statement constructor now accepts optional prepFlags parameter + 2012 Mar 30 - Start of a new thin C++ SQLite wrapper diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index e92e2898..ac00cc25 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -106,6 +106,15 @@ SQLITECPP_API extern const int OPEN_PRIVATECACHE; // SQLITE_OPEN_PRIVATECACHE /// Database filename is not allowed to be a symbolic link (Note: only since SQlite 3.31.0 from 2020-01-22) SQLITECPP_API extern const int OPEN_NOFOLLOW; // SQLITE_OPEN_NOFOLLOW +/// Prepared statement will be retained and reused many times (hint to the query planner) +SQLITECPP_API extern const int PREPARE_PERSISTENT; // SQLITE_PREPARE_PERSISTENT +/// Normalize the prepared statement (no-op, kept for compatibility) +SQLITECPP_API extern const int PREPARE_NORMALIZE; // SQLITE_PREPARE_NORMALIZE +/// Return error if statement uses any virtual tables +SQLITECPP_API extern const int PREPARE_NO_VTAB; // SQLITE_PREPARE_NO_VTAB +/// Prevents SQL compiler errors from being sent to the error log (useful for testing SQL syntax) +SQLITECPP_API extern const int PREPARE_DONT_LOG; // SQLITE_PREPARE_DONT_LOG + SQLITECPP_API extern const int OK; ///< SQLITE_OK (used by check() bellow) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 3b9b0a70..3be985c1 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -57,21 +57,23 @@ class SQLITECPP_API Statement * * @param[in] aDatabase the SQLite Database Connection * @param[in] apQuery an UTF-8 encoded query string + * @param[in] aPrepFlags Optional prepare flags (e.g., SQLite::PREPARE_PERSISTENT) * * Exception is thrown in case of error, then the Statement object is NOT constructed. */ - Statement(const Database& aDatabase, const char* apQuery); + Statement(const Database& aDatabase, const char* apQuery, const unsigned int aPrepFlags = 0); /** * @brief Compile and register the SQL query for the provided SQLite Database Connection * * @param[in] aDatabase the SQLite Database Connection * @param[in] aQuery an UTF-8 encoded query string + * @param[in] aPrepFlags Optional prepare flags (e.g., SQLite::PREPARE_PERSISTENT) * * Exception is thrown in case of error, then the Statement object is NOT constructed. */ - Statement(const Database& aDatabase, const std::string& aQuery) : - Statement(aDatabase, aQuery.c_str()) + Statement(const Database& aDatabase, const std::string& aQuery, const unsigned int aPrepFlags = 0) : + Statement(aDatabase, aQuery.c_str(), aPrepFlags) {} // Statement is non-copyable @@ -699,9 +701,10 @@ class SQLITECPP_API Statement /** * @brief Prepare statement object. * + * @param[in] aPrepFlags Optional prepare flags (e.g., SQLite::PREPARE_PERSISTENT) * @return Shared pointer to prepared statement object */ - TStatementPtr prepareStatement(); + TStatementPtr prepareStatement(const unsigned int aPrepFlags); /** * @brief Return a prepared statement object. diff --git a/src/Database.cpp b/src/Database.cpp index 30f71158..8040d8ae 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -43,6 +43,19 @@ const int OPEN_NOFOLLOW = SQLITE_OPEN_NOFOLLOW; const int OPEN_NOFOLLOW = 0; #endif +// Prepared statement flags (available since SQLite 3.20.0) +#if SQLITE_VERSION_NUMBER >= 3020000 +const int PREPARE_PERSISTENT = SQLITE_PREPARE_PERSISTENT; +const int PREPARE_NORMALIZE = SQLITE_PREPARE_NORMALIZE; +const int PREPARE_NO_VTAB = SQLITE_PREPARE_NO_VTAB; +const int PREPARE_DONT_LOG = SQLITE_PREPARE_DONT_LOG; +#else +const int PREPARE_PERSISTENT = 0; +const int PREPARE_NORMALIZE = 0; +const int PREPARE_NO_VTAB = 0; +const int PREPARE_DONT_LOG = 0; +#endif + const char* const VERSION = SQLITE_VERSION; const int VERSION_NUMBER = SQLITE_VERSION_NUMBER; diff --git a/src/Statement.cpp b/src/Statement.cpp index e4a264f4..073cd367 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -31,10 +31,10 @@ namespace SQLite { -Statement::Statement(const Database& aDatabase, const char* apQuery) : +Statement::Statement(const Database& aDatabase, const char* apQuery, const unsigned int aPrepFlags) : mQuery(apQuery), mpSQLite(aDatabase.getHandle()), - mpPreparedStatement(prepareStatement()) // prepare the SQL query (needs Database friendship) + mpPreparedStatement(prepareStatement(aPrepFlags)) // prepare the SQL query (needs Database friendship) { mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); } @@ -357,10 +357,20 @@ std::string Statement::getExpandedSQL() const { // Prepare SQLite statement object and return shared pointer to this object -Statement::TStatementPtr Statement::prepareStatement() +Statement::TStatementPtr Statement::prepareStatement(const unsigned int aPrepFlags) { sqlite3_stmt* statement; - const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast(mQuery.size()), &statement, nullptr); + int ret; + + // Use sqlite3_prepare_v3 when available (SQLite >= 3.20.0), otherwise fall back to sqlite3_prepare_v2 +#if SQLITE_VERSION_NUMBER >= 3020000 + ret = sqlite3_prepare_v3(mpSQLite, mQuery.c_str(), static_cast(mQuery.size()), aPrepFlags, &statement, nullptr); +#else + // sqlite3_prepare_v3 not available, ignore prepFlags and use sqlite3_prepare_v2 + (void)aPrepFlags; // Avoid unused parameter warning + ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast(mQuery.size()), &statement, nullptr); +#endif + if (SQLITE_OK != ret) { throw SQLite::Exception(mpSQLite, ret); diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index a76d06a8..5bccd9e5 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -1026,3 +1026,39 @@ TEST(Statement, getBindParameterCount) SQLite::Statement query3(db, "SELECT id, msg FROM test"); EXPECT_EQ(0, query3.getBindParameterCount()); } + +TEST(Statement, prepareFlags) +{ + // 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)")); + + // Test creating a statement with PREPARE_PERSISTENT flag + SQLite::Statement query1(db, "SELECT * FROM test", SQLite::PREPARE_PERSISTENT); + EXPECT_STREQ("SELECT * FROM test", query1.getQuery().c_str()); + EXPECT_EQ(2, query1.getColumnCount()); + + // Test creating a statement with PREPARE_DONT_LOG flag + // This should not throw even though we're testing SQL syntax + SQLite::Statement query2(db, "SELECT * FROM test", SQLite::PREPARE_DONT_LOG); + EXPECT_STREQ("SELECT * FROM test", query2.getQuery().c_str()); + EXPECT_EQ(2, query2.getColumnCount()); + + // Test creating a statement with combined flags + SQLite::Statement query3(db, "SELECT * FROM test", + SQLite::PREPARE_PERSISTENT | SQLite::PREPARE_DONT_LOG); + EXPECT_STREQ("SELECT * FROM test", query3.getQuery().c_str()); + EXPECT_EQ(2, query3.getColumnCount()); + + // Test that the statement works correctly + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (1, 'hello')")); + EXPECT_TRUE(query3.executeStep()); + EXPECT_EQ(1, query3.getColumn(0).getInt()); + EXPECT_STREQ("hello", query3.getColumn(1).getText()); + + // Test PREPARE_DONT_LOG with invalid SQL (should throw but not log) + // We can't easily verify it's not logged, but we can verify the exception is still thrown + EXPECT_THROW(SQLite::Statement(db, "SELECT * FROM nonexistent", SQLite::PREPARE_DONT_LOG), + SQLite::Exception); +} +