diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index bf5760ab..a6b20998 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -16,6 +16,9 @@ #include #include +#if __cplusplus >= 201703L // C++17 +#include +#endif // Forward declarations to avoid inclusion of in a header struct sqlite3_stmt; @@ -103,6 +106,18 @@ class SQLITECPP_API Column */ std::string getString() const; +#if __cplusplus >= 201703L // C++17 + /** + * @brief Return a std::string_view for a TEXT or BLOB column. + * + * Note this correctly handles strings that contain null bytes. + * + * @warning returned string_view is only valid until there is a type + * conversion or the statement is stepped or reset. + */ + std::string_view getStringView() const; +#endif + /** * @brief Return the type of the value of the column using sqlite3_column_type() * diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index e92e2898..817db9e1 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -523,63 +523,99 @@ class SQLITECPP_API Database void loadExtension(const char* apExtensionName, const char* apEntryPointName); /** - * @brief Set the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_key call and should thus be called - * directly after opening the database. - * Open encrypted database -> call db.key("secret") -> database ready - * - * @param[in] aKey Key to decode/encode the database - * - * @throw SQLite::Exception in case of error - */ - void key(const std::string& aKey) const; + * @brief Set the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_key call and should thus be called + * directly after opening the database. + * Open encrypted database -> call db.key("secret") -> database ready + * + * @param[in] aKey Key to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void key(const char* apKey, const int aSize ) const; + + /** + * @brief Set the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_key call and should thus be called + * directly after opening the database. + * Open encrypted database -> call db.key("secret") -> database ready + * + * @param[in] aKey Key to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void key(const std::string& aKey) const + { + key(aKey.data(), aKey.size()); + } + + /** + * @brief Reset the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_rekey call and should thus be called + * after the database has been opened with a valid key. To decrypt a + * database, call this method with an empty string. + * Open normal database -> call db.rekey("secret") -> encrypted database, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready + * + * @param[in] apNewKey New key to encode the database + * @param[in] aSize Length of apNewKey + * + * @throw SQLite::Exception in case of error + */ + void rekey(const char* apNewKey, const int aSize ) const; /** - * @brief Reset the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_rekey call and should thus be called - * after the database has been opened with a valid key. To decrypt a - * database, call this method with an empty string. - * Open normal database -> call db.rekey("secret") -> encrypted database, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready - * - * @param[in] aNewKey New key to encode the database - * - * @throw SQLite::Exception in case of error - */ - void rekey(const std::string& aNewKey) const; + * @brief Reset the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_rekey call and should thus be called + * after the database has been opened with a valid key. To decrypt a + * database, call this method with an empty string. + * Open normal database -> call db.rekey("secret") -> encrypted database, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready + * + * @param[in] aNewKey New key to encode the database + * + * @throw SQLite::Exception in case of error + */ + void rekey(const std::string& aNewKey) const + { + rekey(aNewKey.data(), aNewKey.size()); + } /** - * @brief Test if a file contains an unencrypted database. - * - * This is a simple test that reads the first bytes of a database file and - * compares them to the standard header for unencrypted databases. If the - * header does not match the standard string, we assume that we have an - * encrypted file. - * - * @param[in] aFilename path/uri to a file - * - * @return true if the database has the standard header. - * - * @throw SQLite::Exception in case of error - */ + * @brief Test if a file contains an unencrypted database. + * + * This is a simple test that reads the first bytes of a database file and + * compares them to the standard header for unencrypted databases. If the + * header does not match the standard string, we assume that we have an + * encrypted file. + * + * @param[in] aFilename path/uri to a file + * + * @return true if the database has the standard header. + * + * @throw SQLite::Exception in case of error + */ static bool isUnencrypted(const std::string& aFilename); /** - * @brief Parse SQLite header data from a database file. - * - * This function reads the first 100 bytes of a SQLite database file - * and reconstructs groups of individual bytes into the associated fields - * in a Header object. - * - * @param[in] aFilename path/uri to a file - * - * @return Header object containing file data - * - * @throw SQLite::Exception in case of error - */ + * @brief Parse SQLite header data from a database file. + * + * This function reads the first 100 bytes of a SQLite database file + * and reconstructs groups of individual bytes into the associated fields + * in a Header object. + * + * @param[in] aFilename path/uri to a file + * + * @return Header object containing file data + * + * @throw SQLite::Exception in case of error + */ static Header getHeaderInfo(const std::string& aFilename); // Parse SQLite header data from a database file. diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 3b9b0a70..a6562ab6 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -18,6 +18,9 @@ #include #include #include +#if __cplusplus >= 201703L // C++17 +#include +#endif // Forward declarations to avoid inclusion of in a header struct sqlite3; @@ -139,6 +142,16 @@ class SQLITECPP_API Statement * @brief Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ void bind(const int aIndex, const double aValue); + +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const int aIndex, const std::string_view aValue); +#endif + /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -157,6 +170,16 @@ class SQLITECPP_API Statement * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ void bind(const int aIndex, const void* apValue, const int aSize); +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const int aIndex, const std::string_view aValue); +#endif /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). * @@ -219,6 +242,17 @@ class SQLITECPP_API Statement { bind(getIndex(apName), aValue); } +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const char* apName, const std::string_view aValue) + { + bind(getIndex(apName), aValue); + } +#endif /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -246,6 +280,19 @@ class SQLITECPP_API Statement { bind(getIndex(apName), apValue, aSize); } +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const char* apName, const std::string_view aValue) + { + bindNoCopy(getIndex(apName), aValue); + } +#endif /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -320,6 +367,18 @@ class SQLITECPP_API Statement { bind(aName.c_str(), aValue); } + +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const std::string& aName, const std::string_view aValue) + { + bind(aName.c_str(), aValue); + } +#endif /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -347,6 +406,21 @@ class SQLITECPP_API Statement { bind(aName.c_str(), apValue, aSize); } + +#if __cplusplus >= 201703L // C++17 + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const std::string& aName, const std::string_view aValue) + { + bindNoCopy(aName.c_str(), aValue); + } +#endif + /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * diff --git a/src/Column.cpp b/src/Column.cpp index c7d18c51..151e16fe 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -101,6 +101,23 @@ std::string Column::getString() const return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); } +#if __cplusplus >= 201703L // C++17 +// Return a std::string_view to a TEXT or BLOB column +std::string_view Column::getStringView() const +{ + // Note: using sqlite3_column_blob and not sqlite3_column_text + // - no need for sqlite3_column_text to add a \0 on the end, as we're getting the bytes length directly + // however, we need to call sqlite3_column_bytes() to ensure correct format. It's a noop on a BLOB + // or a TEXT value with the correct encoding (UTF-8). Otherwise it'll do a conversion to TEXT (UTF-8). + (void)sqlite3_column_bytes(mStmtPtr.get(), mIndex); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + + // SQLite docs: "The safest policy is to invoke… sqlite3_column_blob() followed by sqlite3_column_bytes()" + // Note: std::string is ok to pass nullptr as first arg, if length is 0 + return std::string_view(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); +} +#endif + // Return the type of the value of the column int Column::getType() const noexcept { diff --git a/src/Database.cpp b/src/Database.cpp index 30f71158..0b0fdfef 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -224,17 +224,18 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi } // Set the key for the current sqlite database instance. -void Database::key(const std::string& aKey) const +void Database::key(const char* apKey, const int aSize) const { - int passLen = static_cast(aKey.length()); #ifdef SQLITE_HAS_CODEC - if (passLen > 0) + if (aSize > 0) { - const int ret = sqlite3_key(getHandle(), aKey.c_str(), passLen); + const int ret = sqlite3_key(getHandle(), apKey, aSize); check(ret); } #else // SQLITE_HAS_CODEC - if (passLen > 0) + // Silence unused parameter warning + static_cast(apKey); + if (aSize > 0) { throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable."); } @@ -242,13 +243,12 @@ void Database::key(const std::string& aKey) const } // Reset the key for the current sqlite database instance. -void Database::rekey(const std::string& aNewKey) const +void Database::rekey(const char* apNewKey, const int aSize) const { #ifdef SQLITE_HAS_CODEC - int passLen = aNewKey.length(); - if (passLen > 0) + if (aSize > 0) { - const int ret = sqlite3_rekey(getHandle(), aNewKey.c_str(), passLen); + const int ret = sqlite3_rekey(getHandle(), apNewKey, aSize); check(ret); } else @@ -257,7 +257,10 @@ void Database::rekey(const std::string& aNewKey) const check(ret); } #else // SQLITE_HAS_CODEC - static_cast(aNewKey); // silence unused parameter warning + // Silence unused parameter warnings + static_cast(apNewKey); + static_cast(aSize); + throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable."); #endif // SQLITE_HAS_CODEC } diff --git a/src/Statement.cpp b/src/Statement.cpp index e4a264f4..68fc23d4 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -108,6 +108,20 @@ void Statement::bind(const int aIndex, const double aValue) check(ret); } +#if __cplusplus >= 201703L // c++17 +/** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ +void Statement::bind(const int aIndex, const std::string_view aValue) { + const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size()), SQLITE_TRANSIENT); + check(ret); +} +#endif + + // 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) { @@ -130,6 +144,16 @@ void Statement::bind(const int aIndex, const void* apValue, const int aSize) check(ret); } +#if __cplusplus >= 201703L // C++17 +// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement +void Statement::bindNoCopy(const int aIndex, const std::string_view aValue) +{ + const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size()), SQLITE_STATIC); + check(ret); +} +#endif + // Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const std::string& aValue) {