diff --git a/Libraries/LibDatabase/Database.cpp b/Libraries/LibDatabase/Database.cpp index b6023e0a69..e95b857c12 100644 --- a/Libraries/LibDatabase/Database.cpp +++ b/Libraries/LibDatabase/Database.cpp @@ -170,8 +170,9 @@ ValueType Database::result_column(StatementID statement_id, int column) auto* statement = prepared_statement(statement_id); if constexpr (IsSame) { + auto length = sqlite3_column_bytes(statement, column); auto const* text = reinterpret_cast(sqlite3_column_text(statement, column)); - return MUST(String::from_utf8(StringView { text, strlen(text) })); + return MUST(String::from_utf8(StringView { text, static_cast(length) })); } else if constexpr (IsSame) { auto length = sqlite3_column_bytes(statement, column); auto const* text = sqlite3_column_blob(statement, column); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index e6106a642b..7b206208ed 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(AK) add_subdirectory(LibCompress) add_subdirectory(LibCore) add_subdirectory(LibCrypto) +add_subdirectory(LibDatabase) add_subdirectory(LibDiff) add_subdirectory(LibDNS) add_subdirectory(LibGC) diff --git a/Tests/LibDatabase/CMakeLists.txt b/Tests/LibDatabase/CMakeLists.txt new file mode 100644 index 0000000000..8c1292a6a2 --- /dev/null +++ b/Tests/LibDatabase/CMakeLists.txt @@ -0,0 +1,7 @@ +set(TEST_SOURCES + TestDatabase.cpp +) + +foreach(source IN LISTS TEST_SOURCES) + ladybird_test("${source}" LibDatabase LIBS LibDatabase) +endforeach() diff --git a/Tests/LibDatabase/TestDatabase.cpp b/Tests/LibDatabase/TestDatabase.cpp new file mode 100644 index 0000000000..00ce4a154b --- /dev/null +++ b/Tests/LibDatabase/TestDatabase.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +TEST_CASE(string_can_contain_null_bytes) +{ + auto database = TRY_OR_FAIL(Database::Database::create_memory_backed()); + + database->execute_statement(TRY_OR_FAIL(database->prepare_statement(R"#( + CREATE TABLE WebStorage ( + key TEXT PRIMARY KEY, + value TEXT + ); + )#"sv)), + {}); + + auto get_item_statement = TRY_OR_FAIL(database->prepare_statement("SELECT value FROM WebStorage WHERE key = ?;"sv)); + auto set_item_statement = TRY_OR_FAIL(database->prepare_statement("INSERT OR REPLACE INTO WebStorage VALUES (?, ?);"sv)); + auto delete_item_statement = TRY_OR_FAIL(database->prepare_statement("DELETE FROM WebStorage WHERE key = ?;"sv)); + + auto get_item = [&](String const& key) { + Optional result; + database->execute_statement( + get_item_statement, + [&](auto statement_id) { + result = database->result_column(statement_id, 0); + }, + key); + return result; + }; + + auto set_item = [&](String const& key, String const& value) { + database->execute_statement( + set_item_statement, + {}, + key, + value); + }; + + auto remove_item = [&](String const& key) { + database->execute_statement( + delete_item_statement, + {}, + key); + }; + + EXPECT_EQ(get_item("my_key"_string), Optional {}); + set_item("my_key"_string, "my_value"_string); + EXPECT_EQ(get_item("my_key"_string), Optional { "my_value"_string }); + set_item("my_key"_string, "my_value_with_\0_null"_string); + EXPECT_EQ(get_item("my_key"_string), Optional { "my_value_with_\0_null"_string }); + remove_item("my_key"_string); + EXPECT_EQ(get_item("my_key"_string), Optional {}); +}