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:
Timothy Flynn 2024-06-04 16:34:32 -04:00 committed by Tim Flynn
commit 30e745ffa7
Notes: sideshowbarker 2024-07-17 18:08:55 +09:00
12 changed files with 206 additions and 304 deletions

View file

@ -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);
}