Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
9 changes: 9 additions & 0 deletions include/SQLiteCpp/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
11 changes: 7 additions & 4 deletions include/SQLiteCpp/Statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions src/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
18 changes: 14 additions & 4 deletions src/Statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -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<int>(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<int>(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<int>(mQuery.size()), &statement, nullptr);
#endif

if (SQLITE_OK != ret)
{
throw SQLite::Exception(mpSQLite, ret);
Expand Down
36 changes: 36 additions & 0 deletions tests/Statement_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}