mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-10 01:59:31 +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: 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
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue