1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-30 07:52:08 +00:00

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.
This commit is contained in:
Jan de Visser 2021-11-05 19:05:59 -04:00 committed by Ali Mohammad Pur
parent 108de5dea0
commit 001949d77a
15 changed files with 355 additions and 140 deletions

View file

@ -18,31 +18,65 @@
namespace SQL {
Database::Database(String name)
: m_heap(Heap::construct(name))
: m_heap(Heap::construct(move(name)))
, m_serializer(m_heap)
, m_schemas(BTree::construct(m_serializer, SchemaDef::index_def()->to_tuple_descriptor(), m_heap->schemas_root()))
, m_tables(BTree::construct(m_serializer, TableDef::index_def()->to_tuple_descriptor(), m_heap->tables_root()))
, m_table_columns(BTree::construct(m_serializer, ColumnDef::index_def()->to_tuple_descriptor(), m_heap->table_columns_root()))
{
}
ErrorOr<void> Database::open()
{
TRY(m_heap->open());
m_schemas = BTree::construct(m_serializer, SchemaDef::index_def()->to_tuple_descriptor(), m_heap->schemas_root());
m_schemas->on_new_root = [&]() {
m_heap->set_schemas_root(m_schemas->root());
};
m_tables = BTree::construct(m_serializer, TableDef::index_def()->to_tuple_descriptor(), m_heap->tables_root());
m_tables->on_new_root = [&]() {
m_heap->set_tables_root(m_tables->root());
};
m_table_columns = BTree::construct(m_serializer, ColumnDef::index_def()->to_tuple_descriptor(), m_heap->table_columns_root());
m_table_columns->on_new_root = [&]() {
m_heap->set_table_columns_root(m_table_columns->root());
};
auto default_schema = get_schema("default");
m_open = true;
auto default_schema = TRY(get_schema("default"));
if (!default_schema) {
default_schema = SchemaDef::construct("default");
add_schema(*default_schema);
TRY(add_schema(*default_schema));
}
return {};
}
void Database::add_schema(SchemaDef const& schema)
Database::~Database()
{
m_schemas->insert(schema.key());
// This crashes if the database can't commit. It's recommended to commit
// before the Database goes out of scope so the application can handle
// errors.
// Maybe we should enforce that by having a VERIFY here that there are no
// pending writes. But that's a new API on Heap so let's not do that right
// now.
if (is_open())
MUST(commit());
}
ErrorOr<void> Database::commit()
{
VERIFY(is_open());
TRY(m_heap->flush());
return {};
}
ErrorOr<void> Database::add_schema(SchemaDef const& schema)
{
VERIFY(is_open());
if (!m_schemas->insert(schema.key())) {
warnln("Duplicate schema name {}"sv, schema.name());
return Error::from_string_literal("Duplicate schema name"sv);
}
return {};
}
Key Database::get_schema_key(String const& schema_name)
@ -52,30 +86,37 @@ Key Database::get_schema_key(String const& schema_name)
return key;
}
RefPtr<SchemaDef> Database::get_schema(String const& schema)
ErrorOr<RefPtr<SchemaDef>> Database::get_schema(String const& schema)
{
VERIFY(is_open());
auto schema_name = schema;
if (schema.is_null() || schema.is_empty())
schema_name = "default";
Key key = get_schema_key(schema_name);
auto schema_def_opt = m_schema_cache.get(key.hash());
if (schema_def_opt.has_value())
return schema_def_opt.value();
if (schema_def_opt.has_value()) {
return RefPtr<SchemaDef>(schema_def_opt.value());
}
auto schema_iterator = m_schemas->find(key);
if (schema_iterator.is_end() || (*schema_iterator != key)) {
return nullptr;
return RefPtr<SchemaDef>(nullptr);
}
auto ret = SchemaDef::construct(*schema_iterator);
m_schema_cache.set(key.hash(), ret);
return ret;
return RefPtr<SchemaDef>(ret);
}
void Database::add_table(TableDef& table)
ErrorOr<void> Database::add_table(TableDef& table)
{
m_tables->insert(table.key());
for (auto& column : table.columns()) {
m_table_columns->insert(column.key());
VERIFY(is_open());
if (!m_tables->insert(table.key())) {
warnln("Duplicate table name '{}'.'{}'"sv, table.parent()->name(), table.name());
return Error::from_string_literal("Duplicate table name"sv);
}
for (auto& column : table.columns()) {
VERIFY(m_table_columns->insert(column.key()));
}
return {};
}
Key Database::get_table_key(String const& schema_name, String const& table_name)
@ -85,36 +126,39 @@ Key Database::get_table_key(String const& schema_name, String const& table_name)
return key;
}
RefPtr<TableDef> Database::get_table(String const& schema, String const& name)
ErrorOr<RefPtr<TableDef>> Database::get_table(String const& schema, String const& name)
{
VERIFY(is_open());
auto schema_name = schema;
if (schema.is_null() || schema.is_empty())
schema_name = "default";
Key key = get_table_key(schema_name, name);
auto table_def_opt = m_table_cache.get(key.hash());
if (table_def_opt.has_value())
return table_def_opt.value();
return RefPtr<TableDef>(table_def_opt.value());
auto table_iterator = m_tables->find(key);
if (table_iterator.is_end() || (*table_iterator != key)) {
return nullptr;
return RefPtr<TableDef>(nullptr);
}
auto schema_def = TRY(get_schema(schema));
if (!schema_def) {
warnln("Schema '{}' does not exist"sv, schema);
return Error::from_string_literal("Schema does not exist"sv);
}
auto schema_def = get_schema(schema);
VERIFY(schema_def);
auto ret = TableDef::construct(schema_def, name);
ret->set_pointer((*table_iterator).pointer());
m_table_cache.set(key.hash(), ret);
auto hash = ret->hash();
auto column_key = ColumnDef::make_key(ret);
for (auto column_iterator = m_table_columns->find(column_key);
!column_iterator.is_end() && ((*column_iterator)["table_hash"].to_u32().value() == hash);
column_iterator++) {
ret->append_column(*column_iterator);
}
return ret;
return RefPtr<TableDef>(ret);
}
Vector<Row> Database::select_all(TableDef const& table)
ErrorOr<Vector<Row>> Database::select_all(TableDef const& table)
{
VERIFY(m_table_cache.get(table.key().hash()).has_value());
Vector<Row> ret;
@ -124,7 +168,7 @@ Vector<Row> Database::select_all(TableDef const& table)
return ret;
}
Vector<Row> Database::match(TableDef const& table, Key const& key)
ErrorOr<Vector<Row>> Database::match(TableDef const& table, Key const& key)
{
VERIFY(m_table_cache.get(table.key().hash()).has_value());
Vector<Row> ret;
@ -140,12 +184,14 @@ Vector<Row> Database::match(TableDef const& table, Key const& key)
return ret;
}
bool Database::insert(Row& row)
ErrorOr<void> Database::insert(Row& row)
{
VERIFY(m_table_cache.get(row.table()->key().hash()).has_value());
// TODO Check constraints
row.set_pointer(m_heap->new_record_pointer());
row.next_pointer(row.table()->pointer());
update(row);
TRY(update(row));
// TODO update indexes defined on table.
@ -153,16 +199,18 @@ bool Database::insert(Row& row)
table_key.set_pointer(row.pointer());
VERIFY(m_tables->update_key_pointer(table_key));
row.table()->set_pointer(row.pointer());
return true;
return {};
}
bool Database::update(Row& tuple)
ErrorOr<void> Database::update(Row& tuple)
{
VERIFY(m_table_cache.get(tuple.table()->key().hash()).has_value());
// TODO Check constraints
m_serializer.reset();
return m_serializer.serialize_and_write<Tuple>(tuple, tuple.pointer());
m_serializer.serialize_and_write<Tuple>(tuple, tuple.pointer());
// TODO update indexes defined on table.
return {};
}
}