1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-19 17:05:06 +00:00
serenity/Userland/Services/SQLServer/DatabaseConnection.cpp
Jan de Visser 001949d77a LibSQL: Improve error handling
The handling of filesystem level errors was basically non-existing or
consisting of `VERIFY_NOT_REACHED` assertions. Addressed this by
* Adding `open` methods to `Heap` and `Database` which return errors.
* Changing the interface of methods of these classes and clients
downstream to propagate these errors.

The constructors of `Heap` and `Database` don't open the underlying
filesystem file anymore.

The SQL statement handlers return an `SQLErrorCode::InternalError`
error code if an error comes back from the lower levels. Note that some
of these errors are things like duplicate index entry errors that should
be caught before the SQL layer attempts to actually update the database.

Added tests to catch attempts to open weird or non-existent files as
databases.

Finally, in between me writing this patch and submitting the PR the
AK::Result<Foo, Bar> template got deprecated in favour of ErrorOr<Foo>.
This resulted in more busywork.
2021-12-04 20:49:22 +03:30

86 lines
3.4 KiB
C++

/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <SQLServer/ClientConnection.h>
#include <SQLServer/DatabaseConnection.h>
#include <SQLServer/SQLStatement.h>
namespace SQLServer {
static HashMap<int, NonnullRefPtr<DatabaseConnection>> s_connections;
RefPtr<DatabaseConnection> DatabaseConnection::connection_for(int connection_id)
{
if (s_connections.contains(connection_id))
return *s_connections.get(connection_id).value();
dbgln_if(SQLSERVER_DEBUG, "Invalid connection_id {}", connection_id);
return nullptr;
}
static int s_next_connection_id = 0;
DatabaseConnection::DatabaseConnection(String database_name, int client_id)
: Object()
, m_database_name(move(database_name))
, m_connection_id(s_next_connection_id++)
, m_client_id(client_id)
{
if (LexicalPath path(m_database_name); (path.title() != m_database_name) || (path.dirname() != ".")) {
auto client_connection = ClientConnection::client_connection_for(m_client_id);
client_connection->async_connection_error(m_connection_id, (int)SQL::SQLErrorCode::InvalidDatabaseName, m_database_name);
return;
}
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection {} initiating connection with database '{}'", connection_id(), m_database_name);
s_connections.set(m_connection_id, *this);
deferred_invoke([this]() {
m_database = SQL::Database::construct(String::formatted("/home/anon/sql/{}.db", m_database_name));
auto client_connection = ClientConnection::client_connection_for(m_client_id);
if (auto maybe_error = m_database->open(); maybe_error.is_error()) {
client_connection->async_connection_error(m_connection_id, (int)SQL::SQLErrorCode::InternalError, maybe_error.error().string_literal());
return;
}
m_accept_statements = true;
if (client_connection)
client_connection->async_connected(m_connection_id, m_database_name);
else
warnln("Cannot notify client of database connection. Client disconnected");
});
}
void DatabaseConnection::disconnect()
{
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::disconnect(connection_id {}, database '{}'", connection_id(), m_database_name);
m_accept_statements = false;
deferred_invoke([this]() {
m_database = nullptr;
s_connections.remove(m_connection_id);
auto client_connection = ClientConnection::client_connection_for(client_id());
if (client_connection)
client_connection->async_disconnected(m_connection_id);
else
warnln("Cannot notify client of database disconnection. Client disconnected");
});
}
int DatabaseConnection::sql_statement(String const& sql)
{
dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::sql_statement(connection_id {}, database '{}', sql '{}'", connection_id(), m_database_name, sql);
auto client_connection = ClientConnection::client_connection_for(client_id());
if (!client_connection) {
warnln("Cannot notify client of database disconnection. Client disconnected");
return -1;
}
if (!m_accept_statements) {
client_connection->async_execution_error(-1, (int)SQL::SQLErrorCode::DatabaseUnavailable, m_database_name);
return -1;
}
auto statement = SQLStatement::construct(*this, sql);
return statement->statement_id();
}
}