From 30e745ffa7305ae79ead0d7555e25e6bccb106df Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 4 Jun 2024 16:34:32 -0400 Subject: [PATCH] LibWebView: Replace usage of LibSQL with sqlite3 This makes WebView::Database wrap around sqlite3 instead of LibSQL. The effect on outside callers is pretty minimal. The main consequences are: 1. We must ensure the Cookie table exists before preparing any SQL statements involving that table. 2. We can use an INSERT OR REPLACE statement instead of separate INSERT and UPDATE statements. --- Ladybird/AppKit/Application/Application.h | 2 - Ladybird/AppKit/Application/Application.mm | 5 - .../AppKit/Application/ApplicationBridge.cpp | 8 - .../AppKit/Application/ApplicationBridge.h | 2 - Ladybird/AppKit/main.mm | 3 +- Ladybird/Qt/main.cpp | 9 +- Userland/Libraries/LibWebView/CMakeLists.txt | 2 +- Userland/Libraries/LibWebView/CookieJar.cpp | 188 +++++------------- Userland/Libraries/LibWebView/CookieJar.h | 17 +- Userland/Libraries/LibWebView/Database.cpp | 186 +++++++++++------ Userland/Libraries/LibWebView/Database.h | 82 +++----- Userland/Utilities/headless-browser.cpp | 6 +- 12 files changed, 206 insertions(+), 304 deletions(-) diff --git a/Ladybird/AppKit/Application/Application.h b/Ladybird/AppKit/Application/Application.h index 2399ed6c522..111eeb6997a 100644 --- a/Ladybird/AppKit/Application/Application.h +++ b/Ladybird/AppKit/Application/Application.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #import @@ -23,7 +22,6 @@ class WebViewBridge; - (instancetype)init; - (ErrorOr)launchRequestServer:(Vector const&)certificates; -- (ErrorOr>)launchSQLServer; - (ErrorOr>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge; - (ErrorOr)launchWebWorker; diff --git a/Ladybird/AppKit/Application/Application.mm b/Ladybird/AppKit/Application/Application.mm index c6711cdd14b..472ad5f8f09 100644 --- a/Ladybird/AppKit/Application/Application.mm +++ b/Ladybird/AppKit/Application/Application.mm @@ -41,11 +41,6 @@ return m_application_bridge->launch_request_server(certificates); } -- (ErrorOr>)launchSQLServer -{ - return m_application_bridge->launch_sql_server(); -} - - (ErrorOr>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge { return m_application_bridge->launch_web_content(web_view_bridge); diff --git a/Ladybird/AppKit/Application/ApplicationBridge.cpp b/Ladybird/AppKit/Application/ApplicationBridge.cpp index 9d6e2d72189..1b248692fd9 100644 --- a/Ladybird/AppKit/Application/ApplicationBridge.cpp +++ b/Ladybird/AppKit/Application/ApplicationBridge.cpp @@ -37,14 +37,6 @@ ErrorOr ApplicationBridge::launch_request_server(Vector const& return {}; } -ErrorOr> ApplicationBridge::launch_sql_server() -{ - auto sql_server_paths = TRY(get_paths_for_helper_process("SQLServer"sv)); - auto sql_client = TRY(launch_sql_server_process(sql_server_paths)); - - return sql_client; -} - ErrorOr> ApplicationBridge::launch_web_content(WebViewBridge& web_view_bridge) { // FIXME: Fail to open the tab, rather than crashing the whole application if this fails diff --git a/Ladybird/AppKit/Application/ApplicationBridge.h b/Ladybird/AppKit/Application/ApplicationBridge.h index 599d989aa6e..7978343272c 100644 --- a/Ladybird/AppKit/Application/ApplicationBridge.h +++ b/Ladybird/AppKit/Application/ApplicationBridge.h @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace Ladybird { @@ -23,7 +22,6 @@ public: ~ApplicationBridge(); ErrorOr launch_request_server(Vector const& certificates); - ErrorOr> launch_sql_server(); ErrorOr> launch_web_content(WebViewBridge&); ErrorOr launch_web_worker(); diff --git a/Ladybird/AppKit/main.mm b/Ladybird/AppKit/main.mm index 788d0741160..a6d270eabe1 100644 --- a/Ladybird/AppKit/main.mm +++ b/Ladybird/AppKit/main.mm @@ -120,8 +120,7 @@ ErrorOr serenity_main(Main::Arguments arguments) WebView::ProcessManager::the().add_process(pid, move(port)); }; - auto sql_client = TRY([application launchSQLServer]); - auto database = TRY(WebView::Database::create(move(sql_client))); + auto database = TRY(WebView::Database::create()); auto cookie_jar = TRY(WebView::CookieJar::create(*database)); // FIXME: Create an abstraction to re-spawn the RequestServer and re-hook up its client hooks to each tab on crash diff --git a/Ladybird/Qt/main.cpp b/Ladybird/Qt/main.cpp index e42b1822a03..c5e90a919a2 100644 --- a/Ladybird/Qt/main.cpp +++ b/Ladybird/Qt/main.cpp @@ -154,13 +154,8 @@ ErrorOr serenity_main(Main::Arguments arguments) #endif RefPtr database; - - if (!disable_sql_database) { - auto sql_server_paths = TRY(get_paths_for_helper_process("SQLServer"sv)); - auto sql_client = TRY(launch_sql_server_process(sql_server_paths)); - - database = TRY(WebView::Database::create(sql_client)); - } + if (!disable_sql_database) + database = TRY(WebView::Database::create()); auto cookie_jar = database ? TRY(WebView::CookieJar::create(*database)) : WebView::CookieJar::create(); diff --git a/Userland/Libraries/LibWebView/CMakeLists.txt b/Userland/Libraries/LibWebView/CMakeLists.txt index 402018492f8..59bce77a88e 100644 --- a/Userland/Libraries/LibWebView/CMakeLists.txt +++ b/Userland/Libraries/LibWebView/CMakeLists.txt @@ -46,7 +46,7 @@ set(GENERATED_SOURCES ) serenity_lib(LibWebView webview) -target_link_libraries(LibWebView PRIVATE LibCore LibFileSystem LibGfx LibIPC LibProtocol LibJS LibWeb LibSQL LibUnicode LibURL) +target_link_libraries(LibWebView PRIVATE LibCore LibFileSystem LibGfx LibIPC LibProtocol LibJS LibWeb LibUnicode LibURL) target_compile_definitions(LibWebView PRIVATE ENABLE_PUBLIC_SUFFIX=$) find_package(SQLite3 REQUIRED) diff --git a/Userland/Libraries/LibWebView/CookieJar.cpp b/Userland/Libraries/LibWebView/CookieJar.cpp index 5192d6d1b65..8f9dbba367a 100644 --- a/Userland/Libraries/LibWebView/CookieJar.cpp +++ b/Userland/Libraries/LibWebView/CookieJar.cpp @@ -11,12 +11,9 @@ #include #include #include -#include -#include #include #include #include -#include #include namespace WebView { @@ -27,11 +24,11 @@ ErrorOr> CookieJar::create(Database& database) { Statements statements {}; - statements.create_table = TRY(database.prepare_statement(R"#( + auto create_table = TRY(database.prepare_statement(MUST(String::formatted(R"#( CREATE TABLE IF NOT EXISTS Cookies ( name TEXT, value TEXT, - same_site INTEGER, + same_site INTEGER CHECK (same_site >= 0 AND same_site <= {}), creation_time INTEGER, last_access_time INTEGER, expiry_time INTEGER, @@ -40,23 +37,13 @@ ErrorOr> CookieJar::create(Database& database) secure BOOLEAN, http_only BOOLEAN, host_only BOOLEAN, - persistent BOOLEAN - );)#"sv)); + persistent BOOLEAN, + PRIMARY KEY(name, domain, path) + );)#", + to_underlying(Web::Cookie::SameSite::Lax))))); + database.execute_statement(create_table, {}); - statements.update_cookie = TRY(database.prepare_statement(R"#( - UPDATE Cookies SET - value=?, - same_site=?, - creation_time=?, - last_access_time=?, - expiry_time=?, - secure=?, - http_only=?, - host_only=?, - persistent=? - WHERE ((name = ?) AND (domain = ?) AND (path = ?));)#"sv)); - - statements.insert_cookie = TRY(database.prepare_statement("INSERT INTO Cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"sv)); + statements.insert_cookie = TRY(database.prepare_statement("INSERT OR REPLACE INTO Cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"sv)); statements.expire_cookie = TRY(database.prepare_statement("DELETE FROM Cookies WHERE (expiry_time < ?);"sv)); statements.select_all_cookies = TRY(database.prepare_statement("SELECT * FROM Cookies;"sv)); @@ -74,8 +61,6 @@ CookieJar::CookieJar(Optional persisted_storage) if (!m_persisted_storage.has_value()) return; - m_persisted_storage->database.execute_statement(m_persisted_storage->statements.create_table, {}, {}, {}); - // FIXME: Make cookie retrieval lazy so we don't need to retrieve all cookies up front. auto cookies = m_persisted_storage->select_all_cookies(); m_transient_storage.set_cookies(move(cookies)); @@ -84,13 +69,10 @@ CookieJar::CookieJar(Optional persisted_storage) static_cast(DATABASE_SYNCHRONIZATION_TIMER.to_milliseconds()), [this]() { auto now = m_transient_storage.purge_expired_cookies(); - m_persisted_storage->database.execute_statement(m_persisted_storage->statements.expire_cookie, {}, {}, {}, now); + m_persisted_storage->database.execute_statement(m_persisted_storage->statements.expire_cookie, {}, now); - // FIXME: Implement "INSERT OR REPLACE" - for (auto const& it : m_transient_storage.take_inserted_cookies()) + for (auto const& it : m_transient_storage.take_dirty_cookies()) m_persisted_storage->insert_cookie(it.value); - for (auto const& it : m_transient_storage.take_updated_cookies()) - m_persisted_storage->update_cookie(it.value); }); m_persisted_storage->synchronization_timer->start(); } @@ -465,70 +447,6 @@ Vector CookieJar::get_matching_cookies(const URL::URL& url, return cookie_list; } -static ErrorOr parse_cookie(ReadonlySpan row) -{ - if (row.size() != 12) - return Error::from_string_view("Incorrect number of columns to parse cookie"sv); - - size_t index = 0; - - auto convert_text = [&](auto& field, StringView name) -> ErrorOr { - auto const& value = row[index++]; - if (value.type() != SQL::SQLType::Text) - return Error::from_string_view(name); - - field = MUST(value.to_string()); - return {}; - }; - - auto convert_bool = [&](auto& field, StringView name) -> ErrorOr { - auto const& value = row[index++]; - if (value.type() != SQL::SQLType::Boolean) - return Error::from_string_view(name); - - field = value.to_bool().value(); - return {}; - }; - - auto convert_time = [&](auto& field, StringView name) -> ErrorOr { - auto const& value = row[index++]; - if (value.type() != SQL::SQLType::Integer) - return Error::from_string_view(name); - - field = value.to_unix_date_time().value(); - return {}; - }; - - auto convert_same_site = [&](auto& field, StringView name) -> ErrorOr { - auto const& value = row[index++]; - if (value.type() != SQL::SQLType::Integer) - return Error::from_string_view(name); - - auto same_site = value.to_int>().value(); - if (same_site > to_underlying(Web::Cookie::SameSite::Lax)) - return Error::from_string_view(name); - - field = static_cast(same_site); - return {}; - }; - - Web::Cookie::Cookie cookie; - TRY(convert_text(cookie.name, "name"sv)); - TRY(convert_text(cookie.value, "value"sv)); - TRY(convert_same_site(cookie.same_site, "same_site"sv)); - TRY(convert_time(cookie.creation_time, "creation_time"sv)); - TRY(convert_time(cookie.last_access_time, "last_access_time"sv)); - TRY(convert_time(cookie.expiry_time, "expiry_time"sv)); - TRY(convert_text(cookie.domain, "domain"sv)); - TRY(convert_text(cookie.path, "path"sv)); - TRY(convert_bool(cookie.secure, "secure"sv)); - TRY(convert_bool(cookie.http_only, "http_only"sv)); - TRY(convert_bool(cookie.host_only, "host_only"sv)); - TRY(convert_bool(cookie.persistent, "persistent"sv)); - - return cookie; -} - void CookieJar::TransientStorage::set_cookies(Cookies cookies) { m_cookies = move(cookies); @@ -537,24 +455,8 @@ void CookieJar::TransientStorage::set_cookies(Cookies cookies) void CookieJar::TransientStorage::set_cookie(CookieStorageKey key, Web::Cookie::Cookie cookie) { - auto result = m_cookies.set(key, cookie); - - switch (result) { - case HashSetResult::InsertedNewEntry: - m_inserted_cookies.set(move(key), move(cookie)); - break; - - case HashSetResult::ReplacedExistingEntry: - if (m_inserted_cookies.contains(key)) - m_inserted_cookies.set(move(key), move(cookie)); - else - m_updated_cookies.set(move(key), move(cookie)); - break; - - case HashSetResult::KeptExistingEntry: - VERIFY_NOT_REACHED(); - break; - } + m_cookies.set(key, cookie); + m_dirty_cookies.set(move(key), move(cookie)); } Optional CookieJar::TransientStorage::get_cookie(CookieStorageKey const& key) @@ -568,8 +470,7 @@ UnixDateTime CookieJar::TransientStorage::purge_expired_cookies() auto is_expired = [&](auto const&, auto const& cookie) { return cookie.expiry_time < now; }; m_cookies.remove_all_matching(is_expired); - m_inserted_cookies.remove_all_matching(is_expired); - m_updated_cookies.remove_all_matching(is_expired); + m_dirty_cookies.remove_all_matching(is_expired); return now; } @@ -578,7 +479,7 @@ void CookieJar::PersistedStorage::insert_cookie(Web::Cookie::Cookie const& cooki { database.execute_statement( statements.insert_cookie, - {}, {}, {}, + {}, cookie.name, cookie.value, to_underlying(cookie.same_site), @@ -593,44 +494,47 @@ void CookieJar::PersistedStorage::insert_cookie(Web::Cookie::Cookie const& cooki cookie.persistent); } -void CookieJar::PersistedStorage::update_cookie(Web::Cookie::Cookie const& cookie) +static Web::Cookie::Cookie parse_cookie(Database& database, Database::StatementID statement_id) { - database.execute_statement( - statements.update_cookie, - {}, {}, {}, - cookie.value, - to_underlying(cookie.same_site), - cookie.creation_time, - cookie.last_access_time, - cookie.expiry_time, - cookie.secure, - cookie.http_only, - cookie.host_only, - cookie.persistent, - cookie.name, - cookie.domain, - cookie.path); + int column = 0; + auto convert_text = [&](auto& field) { field = database.result_column(statement_id, column++); }; + auto convert_bool = [&](auto& field) { field = database.result_column(statement_id, column++); }; + auto convert_time = [&](auto& field) { field = database.result_column(statement_id, column++); }; + + auto convert_same_site = [&](auto& field) { + auto same_site = database.result_column>(statement_id, column++); + field = static_cast(same_site); + }; + + Web::Cookie::Cookie cookie; + convert_text(cookie.name); + convert_text(cookie.value); + convert_same_site(cookie.same_site); + convert_time(cookie.creation_time); + convert_time(cookie.last_access_time); + convert_time(cookie.expiry_time); + convert_text(cookie.domain); + convert_text(cookie.path); + convert_bool(cookie.secure); + convert_bool(cookie.http_only); + convert_bool(cookie.host_only); + convert_bool(cookie.persistent); + + return cookie; } CookieJar::TransientStorage::Cookies CookieJar::PersistedStorage::select_all_cookies() { HashMap cookies; - auto add_cookie = [&](auto cookie) { - CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - cookies.set(move(key), move(cookie)); - }; - database.execute_statement( statements.select_all_cookies, - [&](auto row) { - if (auto cookie = parse_cookie(row); cookie.is_error()) - dbgln("Failed to parse cookie '{}': {}", cookie.error(), row); - else - add_cookie(cookie.release_value()); - }, - {}, - {}); + [&](auto statement_id) { + auto cookie = parse_cookie(database, statement_id); + + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; + cookies.set(move(key), move(cookie)); + }); return cookies; } diff --git a/Userland/Libraries/LibWebView/CookieJar.h b/Userland/Libraries/LibWebView/CookieJar.h index 9f38de99773..f5d5684776c 100644 --- a/Userland/Libraries/LibWebView/CookieJar.h +++ b/Userland/Libraries/LibWebView/CookieJar.h @@ -14,10 +14,10 @@ #include #include #include -#include #include #include #include +#include #include namespace WebView { @@ -32,11 +32,9 @@ struct CookieStorageKey { class CookieJar { struct Statements { - SQL::StatementID create_table { 0 }; - SQL::StatementID insert_cookie { 0 }; - SQL::StatementID update_cookie { 0 }; - SQL::StatementID expire_cookie { 0 }; - SQL::StatementID select_all_cookies { 0 }; + Database::StatementID insert_cookie { 0 }; + Database::StatementID expire_cookie { 0 }; + Database::StatementID select_all_cookies { 0 }; }; class TransientStorage { @@ -51,8 +49,7 @@ class CookieJar { UnixDateTime purge_expired_cookies(); - auto take_inserted_cookies() { return move(m_inserted_cookies); } - auto take_updated_cookies() { return move(m_updated_cookies); } + auto take_dirty_cookies() { return move(m_dirty_cookies); } template void for_each_cookie(Callback callback) @@ -63,13 +60,11 @@ class CookieJar { private: Cookies m_cookies; - Cookies m_inserted_cookies; - Cookies m_updated_cookies; + Cookies m_dirty_cookies; }; struct PersistedStorage { void insert_cookie(Web::Cookie::Cookie const& cookie); - void update_cookie(Web::Cookie::Cookie const& cookie); TransientStorage::Cookies select_all_cookies(); Database& database; diff --git a/Userland/Libraries/LibWebView/Database.cpp b/Userland/Libraries/LibWebView/Database.cpp index 66cc4d56c3c..7964a7afe86 100644 --- a/Userland/Libraries/LibWebView/Database.cpp +++ b/Userland/Libraries/LibWebView/Database.cpp @@ -4,85 +4,149 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include +#include +#include +#include +#include #include +#include + namespace WebView { -static constexpr auto database_name = "Browser"sv; +static constexpr StringView sql_error(int error_code) +{ + char const* _sql_error = sqlite3_errstr(error_code); + return { _sql_error, __builtin_strlen(_sql_error) }; +} + +#define SQL_TRY(expression) \ + ({ \ + /* Ignore -Wshadow to allow nesting the macro. */ \ + AK_IGNORE_DIAGNOSTIC("-Wshadow", auto _sql_result = (expression)); \ + if (_sql_result != SQLITE_OK) [[unlikely]] \ + return Error::from_string_view(sql_error(_sql_result)); \ + }) + +#define SQL_MUST(expression) \ + ({ \ + /* Ignore -Wshadow to allow nesting the macro. */ \ + AK_IGNORE_DIAGNOSTIC("-Wshadow", auto _sql_result = (expression)); \ + if (_sql_result != SQLITE_OK) [[unlikely]] { \ + warnln("\033[31;1mDatabase error\033[0m: {}: {}", sql_error(_sql_result), sqlite3_errmsg(m_database)); \ + VERIFY_NOT_REACHED(); \ + } \ + }) ErrorOr> Database::create() { - auto sql_client = TRY(SQL::SQLClient::try_create()); - return create(move(sql_client)); + // FIXME: Move this to a generic "Ladybird data directory" helper. + auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::data_directory()); + TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes)); + + auto database_file = ByteString::formatted("{}/Ladybird.db", database_path); + + sqlite3* m_database { nullptr }; + SQL_TRY(sqlite3_open(database_file.characters(), &m_database)); + + return adopt_nonnull_ref_or_enomem(new (nothrow) Database(m_database)); } -ErrorOr> Database::create(NonnullRefPtr sql_client) +Database::Database(sqlite3* database) + : m_database(database) { - auto connection_id = sql_client->connect(database_name); - if (!connection_id.has_value()) - return Error::from_string_view("Could not connect to SQL database"sv); - - return adopt_nonnull_ref_or_enomem(new (nothrow) Database(move(sql_client), *connection_id)); + VERIFY(m_database); } -Database::Database(NonnullRefPtr sql_client, SQL::ConnectionID connection_id) - : m_sql_client(move(sql_client)) - , m_connection_id(connection_id) +Database::~Database() { - m_sql_client->on_execution_success = [this](auto result) { - if (result.has_results) + for (auto* prepared_statement : m_prepared_statements) + sqlite3_finalize(prepared_statement); + + sqlite3_close(m_database); +} + +ErrorOr Database::prepare_statement(StringView statement) +{ + sqlite3_stmt* prepared_statement { nullptr }; + SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast(statement.length()), &prepared_statement, nullptr)); + + auto statement_id = m_prepared_statements.size(); + m_prepared_statements.append(prepared_statement); + + return statement_id; +} + +void Database::execute_statement(StatementID statement_id, OnResult on_result) +{ + auto* statement = prepared_statement(statement_id); + + while (true) { + auto result = sqlite3_step(statement); + + switch (result) { + case SQLITE_DONE: + SQL_MUST(sqlite3_reset(statement)); return; - if (auto in_progress_statement = take_pending_execution(result); in_progress_statement.has_value()) { - if (in_progress_statement->on_complete) - in_progress_statement->on_complete(); - } - }; + case SQLITE_ROW: + if (on_result) + on_result(statement_id); + continue; - m_sql_client->on_next_result = [this](auto result) { - if (auto in_progress_statement = take_pending_execution(result); in_progress_statement.has_value()) { - if (in_progress_statement->on_result) - in_progress_statement->on_result(result.values); - - m_pending_executions.set({ result.statement_id, result.execution_id }, in_progress_statement.release_value()); - } - }; - - m_sql_client->on_results_exhausted = [this](auto result) { - if (auto in_progress_statement = take_pending_execution(result); in_progress_statement.has_value()) { - if (in_progress_statement->on_complete) - in_progress_statement->on_complete(); - } - }; - - m_sql_client->on_execution_error = [this](auto result) { - if (auto in_progress_statement = take_pending_execution(result); in_progress_statement.has_value()) { - if (in_progress_statement->on_error) - in_progress_statement->on_error(result.error_message); - } - }; -} - -ErrorOr Database::prepare_statement(StringView statement) -{ - if (auto statement_id = m_sql_client->prepare_statement(m_connection_id, statement); statement_id.has_value()) - return *statement_id; - return Error::from_string_view(statement); -} - -void Database::execute_statement(SQL::StatementID statement_id, Vector placeholder_values, PendingExecution pending_execution) -{ - Core::deferred_invoke([this, statement_id, placeholder_values = move(placeholder_values), pending_execution = move(pending_execution)]() mutable { - auto execution_id = m_sql_client->execute_statement(statement_id, move(placeholder_values)); - if (!execution_id.has_value()) { - if (pending_execution.on_error) - pending_execution.on_error("Could not execute statement"sv); + default: + SQL_MUST(result); return; } - - m_pending_executions.set({ statement_id, *execution_id }, move(pending_execution)); - }); + } } +template +void Database::apply_placeholder(StatementID statement_id, int index, ValueType const& value) +{ + auto* statement = prepared_statement(statement_id); + + if constexpr (IsSame) { + StringView string { value }; + SQL_MUST(sqlite3_bind_text(statement, index, string.characters_without_null_termination(), static_cast(string.length()), SQLITE_TRANSIENT)); + } else if constexpr (IsSame) { + SQL_MUST(sqlite3_bind_int64(statement, index, value.offset_to_epoch().to_milliseconds())); + } else if constexpr (IsSame) { + SQL_MUST(sqlite3_bind_int(statement, index, value)); + } else if constexpr (IsSame) { + SQL_MUST(sqlite3_bind_int(statement, index, static_cast(value))); + } +} + +template void Database::apply_placeholder(StatementID, int, String const&); +template void Database::apply_placeholder(StatementID, int, UnixDateTime const&); +template void Database::apply_placeholder(StatementID, int, int const&); +template void Database::apply_placeholder(StatementID, int, bool const&); + +template +ValueType Database::result_column(StatementID statement_id, int column) +{ + auto* statement = prepared_statement(statement_id); + + if constexpr (IsSame) { + auto const* text = reinterpret_cast(sqlite3_column_text(statement, column)); + return MUST(String::from_utf8(StringView { text, strlen(text) })); + } else if constexpr (IsSame) { + auto milliseconds = sqlite3_column_int64(statement, column); + return UnixDateTime::from_milliseconds_since_epoch(milliseconds); + } else if constexpr (IsSame) { + return sqlite3_column_int(statement, column); + } else if constexpr (IsSame) { + return static_cast(sqlite3_column_int(statement, column)); + } + + VERIFY_NOT_REACHED(); +} + +template String Database::result_column(StatementID, int); +template UnixDateTime Database::result_column(StatementID, int); +template int Database::result_column(StatementID, int); +template bool Database::result_column(StatementID, int); + } diff --git a/Userland/Libraries/LibWebView/Database.h b/Userland/Libraries/LibWebView/Database.h index 3508f241e23..ea257217de8 100644 --- a/Userland/Libraries/LibWebView/Database.h +++ b/Userland/Libraries/LibWebView/Database.h @@ -9,87 +9,53 @@ #include #include -#include #include -#include #include #include #include -#include -#include -#include -#include + +struct sqlite3; +struct sqlite3_stmt; namespace WebView { class Database : public RefCounted { - using OnResult = Function)>; - using OnComplete = Function; - using OnError = Function; - public: static ErrorOr> create(); - static ErrorOr> create(NonnullRefPtr); + ~Database(); - ErrorOr prepare_statement(StringView statement); + using StatementID = size_t; + using OnResult = Function; + + ErrorOr prepare_statement(StringView statement); + void execute_statement(StatementID, OnResult on_result); template - void execute_statement(SQL::StatementID statement_id, OnResult on_result, OnComplete on_complete, OnError on_error, PlaceholderValues&&... placeholder_values) + void execute_statement(StatementID statement_id, OnResult on_result, PlaceholderValues&&... placeholder_values) { - auto sync_promise = Core::Promise::construct(); + int index = 1; + (apply_placeholder(statement_id, index++, forward(placeholder_values)), ...); - PendingExecution pending_execution { - .on_result = move(on_result), - .on_complete = [sync_promise, on_complete = move(on_complete)] { - if (on_complete) - on_complete(); - sync_promise->resolve({}); }, - .on_error = [sync_promise, on_error = move(on_error)](auto message) { - if (on_error) - on_error(message); - sync_promise->resolve({}); }, - }; - - Vector values { SQL::Value(forward(placeholder_values))... }; - execute_statement(statement_id, move(values), move(pending_execution)); - - MUST(sync_promise->await()); + execute_statement(statement_id, move(on_result)); } + template + ValueType result_column(StatementID, int column); + private: - struct ExecutionKey { - constexpr bool operator==(ExecutionKey const&) const = default; + explicit Database(sqlite3*); - SQL::StatementID statement_id { 0 }; - SQL::ExecutionID execution_id { 0 }; - }; + template + void apply_placeholder(StatementID statement_id, int index, ValueType const& value); - struct PendingExecution { - OnResult on_result { nullptr }; - OnComplete on_complete { nullptr }; - OnError on_error { nullptr }; - }; - - struct ExecutionKeyTraits : public Traits { - static constexpr unsigned hash(ExecutionKey const& key) - { - return pair_int_hash(u64_hash(key.statement_id), u64_hash(key.execution_id)); - } - }; - - Database(NonnullRefPtr sql_client, SQL::ConnectionID connection_id); - void execute_statement(SQL::StatementID statement_id, Vector placeholder_values, PendingExecution pending_execution); - - template - auto take_pending_execution(ResultData const& result_data) + ALWAYS_INLINE sqlite3_stmt* prepared_statement(StatementID statement_id) { - return m_pending_executions.take({ result_data.statement_id, result_data.execution_id }); + VERIFY(statement_id < m_prepared_statements.size()); + return m_prepared_statements[statement_id]; } - NonnullRefPtr m_sql_client; - SQL::ConnectionID m_connection_id { 0 }; - - HashMap m_pending_executions; + sqlite3* m_database { nullptr }; + Vector m_prepared_statements; }; } diff --git a/Userland/Utilities/headless-browser.cpp b/Userland/Utilities/headless-browser.cpp index 0e9b3635337..7e360d93935 100644 --- a/Userland/Utilities/headless-browser.cpp +++ b/Userland/Utilities/headless-browser.cpp @@ -70,18 +70,14 @@ public: RefPtr request_client; #if defined(AK_OS_SERENITY) - auto database = TRY(WebView::Database::create()); (void)resources_folder; (void)certificates; #else - auto sql_server_paths = TRY(get_paths_for_helper_process("SQLServer"sv)); - auto sql_client = TRY(launch_sql_server_process(sql_server_paths)); - auto database = TRY(WebView::Database::create(move(sql_client))); - auto request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); request_client = TRY(launch_request_server_process(request_server_paths, resources_folder, certificates)); #endif + auto database = TRY(WebView::Database::create()); auto cookie_jar = TRY(WebView::CookieJar::create(*database)); auto view = TRY(adopt_nonnull_own_or_enomem(new (nothrow) HeadlessWebContentView(move(database), move(cookie_jar), request_client)));