mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
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.
This commit is contained in:
parent
65ddd0553b
commit
30e745ffa7
Notes:
sideshowbarker
2024-07-17 18:08:55 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/30e745ffa7 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/45 Reviewed-by: https://github.com/ADKaster ✅
12 changed files with 206 additions and 304 deletions
|
@ -9,7 +9,6 @@
|
|||
#include <AK/Error.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibSQL/SQLClient.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
#import <System/Cocoa.h>
|
||||
|
@ -23,7 +22,6 @@ class WebViewBridge;
|
|||
- (instancetype)init;
|
||||
|
||||
- (ErrorOr<void>)launchRequestServer:(Vector<ByteString> const&)certificates;
|
||||
- (ErrorOr<NonnullRefPtr<SQL::SQLClient>>)launchSQLServer;
|
||||
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge;
|
||||
- (ErrorOr<IPC::File>)launchWebWorker;
|
||||
|
||||
|
|
|
@ -41,11 +41,6 @@
|
|||
return m_application_bridge->launch_request_server(certificates);
|
||||
}
|
||||
|
||||
- (ErrorOr<NonnullRefPtr<SQL::SQLClient>>)launchSQLServer
|
||||
{
|
||||
return m_application_bridge->launch_sql_server();
|
||||
}
|
||||
|
||||
- (ErrorOr<NonnullRefPtr<WebView::WebContentClient>>)launchWebContent:(Ladybird::WebViewBridge&)web_view_bridge
|
||||
{
|
||||
return m_application_bridge->launch_web_content(web_view_bridge);
|
||||
|
|
|
@ -37,14 +37,6 @@ ErrorOr<void> ApplicationBridge::launch_request_server(Vector<ByteString> const&
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<SQL::SQLClient>> 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<NonnullRefPtr<WebView::WebContentClient>> ApplicationBridge::launch_web_content(WebViewBridge& web_view_bridge)
|
||||
{
|
||||
// FIXME: Fail to open the tab, rather than crashing the whole application if this fails
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
#include <LibSQL/SQLClient.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
@ -23,7 +22,6 @@ public:
|
|||
~ApplicationBridge();
|
||||
|
||||
ErrorOr<void> launch_request_server(Vector<ByteString> const& certificates);
|
||||
ErrorOr<NonnullRefPtr<SQL::SQLClient>> launch_sql_server();
|
||||
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content(WebViewBridge&);
|
||||
ErrorOr<IPC::File> launch_web_worker();
|
||||
|
||||
|
|
|
@ -120,8 +120,7 @@ ErrorOr<int> 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
|
||||
|
|
|
@ -154,13 +154,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
#endif
|
||||
|
||||
RefPtr<WebView::Database> 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();
|
||||
|
||||
|
|
|
@ -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=$<BOOL:${ENABLE_PUBLIC_SUFFIX_DOWNLOAD}>)
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
|
|
@ -11,12 +11,9 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Time.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibSQL/TupleDescriptor.h>
|
||||
#include <LibSQL/Value.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWebView/CookieJar.h>
|
||||
#include <LibWebView/Database.h>
|
||||
#include <LibWebView/URL.h>
|
||||
|
||||
namespace WebView {
|
||||
|
@ -27,11 +24,11 @@ ErrorOr<NonnullOwnPtr<CookieJar>> 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<NonnullOwnPtr<CookieJar>> 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<PersistedStorage> 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<PersistedStorage> persisted_storage)
|
|||
static_cast<int>(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<Web::Cookie::Cookie> CookieJar::get_matching_cookies(const URL::URL& url,
|
|||
return cookie_list;
|
||||
}
|
||||
|
||||
static ErrorOr<Web::Cookie::Cookie> parse_cookie(ReadonlySpan<SQL::Value> 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
auto const& value = row[index++];
|
||||
if (value.type() != SQL::SQLType::Integer)
|
||||
return Error::from_string_view(name);
|
||||
|
||||
auto same_site = value.to_int<UnderlyingType<Web::Cookie::SameSite>>().value();
|
||||
if (same_site > to_underlying(Web::Cookie::SameSite::Lax))
|
||||
return Error::from_string_view(name);
|
||||
|
||||
field = static_cast<Web::Cookie::SameSite>(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<Web::Cookie::Cookie> 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<String>(statement_id, column++); };
|
||||
auto convert_bool = [&](auto& field) { field = database.result_column<bool>(statement_id, column++); };
|
||||
auto convert_time = [&](auto& field) { field = database.result_column<UnixDateTime>(statement_id, column++); };
|
||||
|
||||
auto convert_same_site = [&](auto& field) {
|
||||
auto same_site = database.result_column<UnderlyingType<Web::Cookie::SameSite>>(statement_id, column++);
|
||||
field = static_cast<Web::Cookie::SameSite>(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<CookieStorageKey, Web::Cookie::Cookie> 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;
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
#include <AK/Traits.h>
|
||||
#include <LibCore/DateTime.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibSQL/Type.h>
|
||||
#include <LibURL/Forward.h>
|
||||
#include <LibWeb/Cookie/Cookie.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWebView/Database.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
|
||||
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<typename Callback>
|
||||
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;
|
||||
|
|
|
@ -4,85 +4,149 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibWebView/Database.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
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<NonnullRefPtr<Database>> 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<NonnullRefPtr<Database>> Database::create(NonnullRefPtr<SQL::SQLClient> 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::SQLClient> 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::StatementID> Database::prepare_statement(StringView statement)
|
||||
{
|
||||
sqlite3_stmt* prepared_statement { nullptr };
|
||||
SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast<int>(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<SQL::StatementID> 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<SQL::Value> 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<typename ValueType>
|
||||
void Database::apply_placeholder(StatementID statement_id, int index, ValueType const& value)
|
||||
{
|
||||
auto* statement = prepared_statement(statement_id);
|
||||
|
||||
if constexpr (IsSame<ValueType, String>) {
|
||||
StringView string { value };
|
||||
SQL_MUST(sqlite3_bind_text(statement, index, string.characters_without_null_termination(), static_cast<int>(string.length()), SQLITE_TRANSIENT));
|
||||
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
|
||||
SQL_MUST(sqlite3_bind_int64(statement, index, value.offset_to_epoch().to_milliseconds()));
|
||||
} else if constexpr (IsSame<ValueType, int>) {
|
||||
SQL_MUST(sqlite3_bind_int(statement, index, value));
|
||||
} else if constexpr (IsSame<ValueType, bool>) {
|
||||
SQL_MUST(sqlite3_bind_int(statement, index, static_cast<int>(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<typename ValueType>
|
||||
ValueType Database::result_column(StatementID statement_id, int column)
|
||||
{
|
||||
auto* statement = prepared_statement(statement_id);
|
||||
|
||||
if constexpr (IsSame<ValueType, String>) {
|
||||
auto const* text = reinterpret_cast<char const*>(sqlite3_column_text(statement, column));
|
||||
return MUST(String::from_utf8(StringView { text, strlen(text) }));
|
||||
} else if constexpr (IsSame<ValueType, UnixDateTime>) {
|
||||
auto milliseconds = sqlite3_column_int64(statement, column);
|
||||
return UnixDateTime::from_milliseconds_since_epoch(milliseconds);
|
||||
} else if constexpr (IsSame<ValueType, int>) {
|
||||
return sqlite3_column_int(statement, column);
|
||||
} else if constexpr (IsSame<ValueType, bool>) {
|
||||
return static_cast<bool>(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);
|
||||
|
||||
}
|
||||
|
|
|
@ -9,87 +9,53 @@
|
|||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibSQL/SQLClient.h>
|
||||
#include <LibSQL/Type.h>
|
||||
#include <LibSQL/Value.h>
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class Database : public RefCounted<Database> {
|
||||
using OnResult = Function<void(ReadonlySpan<SQL::Value>)>;
|
||||
using OnComplete = Function<void()>;
|
||||
using OnError = Function<void(StringView)>;
|
||||
|
||||
public:
|
||||
static ErrorOr<NonnullRefPtr<Database>> create();
|
||||
static ErrorOr<NonnullRefPtr<Database>> create(NonnullRefPtr<SQL::SQLClient>);
|
||||
~Database();
|
||||
|
||||
ErrorOr<SQL::StatementID> prepare_statement(StringView statement);
|
||||
using StatementID = size_t;
|
||||
using OnResult = Function<void(StatementID)>;
|
||||
|
||||
ErrorOr<StatementID> prepare_statement(StringView statement);
|
||||
void execute_statement(StatementID, OnResult on_result);
|
||||
|
||||
template<typename... PlaceholderValues>
|
||||
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<Empty>::construct();
|
||||
int index = 1;
|
||||
(apply_placeholder(statement_id, index++, forward<PlaceholderValues>(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<SQL::Value> values { SQL::Value(forward<PlaceholderValues>(placeholder_values))... };
|
||||
execute_statement(statement_id, move(values), move(pending_execution));
|
||||
|
||||
MUST(sync_promise->await());
|
||||
execute_statement(statement_id, move(on_result));
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
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<typename ValueType>
|
||||
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<ExecutionKey> {
|
||||
static constexpr unsigned hash(ExecutionKey const& key)
|
||||
{
|
||||
return pair_int_hash(u64_hash(key.statement_id), u64_hash(key.execution_id));
|
||||
}
|
||||
};
|
||||
|
||||
Database(NonnullRefPtr<SQL::SQLClient> sql_client, SQL::ConnectionID connection_id);
|
||||
void execute_statement(SQL::StatementID statement_id, Vector<SQL::Value> placeholder_values, PendingExecution pending_execution);
|
||||
|
||||
template<typename ResultData>
|
||||
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<SQL::SQLClient> m_sql_client;
|
||||
SQL::ConnectionID m_connection_id { 0 };
|
||||
|
||||
HashMap<ExecutionKey, PendingExecution, ExecutionKeyTraits> m_pending_executions;
|
||||
sqlite3* m_database { nullptr };
|
||||
Vector<sqlite3_stmt*> m_prepared_statements;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -70,18 +70,14 @@ public:
|
|||
RefPtr<Protocol::RequestClient> 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)));
|
||||
|
|
Loading…
Reference in a new issue