Skip to content

Commit

Permalink
LibSQL: Improve error handling
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JanDeVisser authored and alimpfard committed Dec 4, 2021
1 parent 108de5d commit 001949d
Show file tree
Hide file tree
Showing 15 changed files with 354 additions and 139 deletions.
4 changes: 4 additions & 0 deletions Tests/LibSQL/TestSqlBtreeIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ void insert_and_get_to_and_from_btree(int num_keys)
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);

Expand All @@ -162,6 +163,7 @@ void insert_and_get_to_and_from_btree(int num_keys)

{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);

Expand All @@ -180,6 +182,7 @@ void insert_into_and_scan_btree(int num_keys)
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);

Expand All @@ -197,6 +200,7 @@ void insert_into_and_scan_btree(int num_keys)

{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto btree = setup_btree(serializer);

Expand Down
85 changes: 68 additions & 17 deletions Tests/LibSQL/TestSqlDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,33 @@ NonnullRefPtr<SQL::TableDef> setup_table(SQL::Database&);
void insert_into_table(SQL::Database&, int);
void verify_table_contents(SQL::Database&, int);
void insert_and_verify(int);
void commit(SQL::Database&);

NonnullRefPtr<SQL::SchemaDef> setup_schema(SQL::Database& db)
{
auto schema = SQL::SchemaDef::construct("TestSchema");
db.add_schema(schema);
auto maybe_error = db.add_schema(schema);
EXPECT(!maybe_error.is_error());
return schema;
}

NonnullRefPtr<SQL::TableDef> setup_table(SQL::Database& db)
{
auto schema = setup_schema(db);
auto table = SQL::TableDef::construct(schema, "TestTable");
db.add_table(table);
table->append_column("TextColumn", SQL::SQLType::Text);
table->append_column("IntColumn", SQL::SQLType::Integer);
EXPECT_EQ(table->num_columns(), 2u);
db.add_table(table);
auto maybe_error = db.add_table(table);
EXPECT(!maybe_error.is_error());
return table;
}

void insert_into_table(SQL::Database& db, int count)
{
auto table = db.get_table("TestSchema", "TestTable");
auto table_or_error = db.get_table("TestSchema", "TestTable");
EXPECT(!table_or_error.is_error());
auto table = table_or_error.value();
EXPECT(table);

for (int ix = 0; ix < count; ix++) {
Expand All @@ -52,18 +56,23 @@ void insert_into_table(SQL::Database& db, int count)

row["TextColumn"] = builder.build();
row["IntColumn"] = ix;
EXPECT(db.insert(row));
auto maybe_error = db.insert(row);
EXPECT(!maybe_error.is_error());
}
}

void verify_table_contents(SQL::Database& db, int expected_count)
{
auto table = db.get_table("TestSchema", "TestTable");
auto table_or_error = db.get_table("TestSchema", "TestTable");
EXPECT(!table_or_error.is_error());
auto table = table_or_error.value();
EXPECT(table);

int sum = 0;
int count = 0;
for (auto& row : db.select_all(*table)) {
auto rows_or_error = db.select_all(*table);
EXPECT(!rows_or_error.is_error());
for (auto& row : rows_or_error.value()) {
StringBuilder builder;
builder.appendff("Test{}", row["IntColumn"].to_int().value());
EXPECT_EQ(row["TextColumn"].to_string(), builder.build());
Expand All @@ -74,21 +83,30 @@ void verify_table_contents(SQL::Database& db, int expected_count)
EXPECT_EQ(sum, (expected_count * (expected_count - 1)) / 2);
}

void commit(SQL::Database& db)
{
auto maybe_error = db.commit();
EXPECT(!maybe_error.is_error());
}

void insert_and_verify(int count)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
setup_table(db);
db->commit();
commit(db);
}
{
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
insert_into_table(db, count);
db->commit();
commit(db);
}
{
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
verify_table_contents(db, count);
}
}
Expand All @@ -97,58 +115,91 @@ TEST_CASE(create_heap)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
EXPECT_EQ(heap->version(), 0x00000001u);
}

TEST_CASE(create_from_dev_random)
{
auto heap = SQL::Heap::construct("/dev/random");
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}

TEST_CASE(create_from_unreadable_file)
{
auto heap = SQL::Heap::construct("/etc/shadow");
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}

TEST_CASE(create_in_non_existing_dir)
{
auto heap = SQL::Heap::construct("/tmp/bogus/test.db");
auto should_be_error = heap->open();
EXPECT(should_be_error.is_error());
}

TEST_CASE(create_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = SQL::Database::construct("/tmp/test.db");
db->commit();
auto should_not_be_error = db->open();
EXPECT(!should_not_be_error.is_error());
commit(db);
}

TEST_CASE(add_schema_to_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
setup_schema(db);
db->commit();
commit(db);
}

TEST_CASE(get_schema_from_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
setup_schema(db);
db->commit();
commit(db);
}
{
auto db = SQL::Database::construct("/tmp/test.db");
auto schema = db->get_schema("TestSchema");
EXPECT(schema);
EXPECT(!db->open().is_error());
auto schema_or_error = db->get_schema("TestSchema");
EXPECT(!schema_or_error.is_error());
EXPECT(schema_or_error.value());
}
}

TEST_CASE(add_table_to_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
setup_table(db);
db->commit();
commit(db);
}

TEST_CASE(get_table_from_database)
{
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto db = SQL::Database::construct("/tmp/test.db");
EXPECT(!db->open().is_error());
setup_table(db);
db->commit();
commit(db);
}
{
auto db = SQL::Database::construct("/tmp/test.db");
auto table = db->get_table("TestSchema", "TestTable");
EXPECT(!db->open().is_error());
auto table_or_error = db->get_table("TestSchema", "TestTable");
EXPECT(!table_or_error.is_error());
auto table = table_or_error.value();
EXPECT(table);
EXPECT_EQ(table->name(), "TestTable");
EXPECT_EQ(table->num_columns(), 2u);
Expand Down
4 changes: 4 additions & 0 deletions Tests/LibSQL/TestSqlHashIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ void insert_and_get_to_and_from_hash_index(int num_keys)
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto hash_index = setup_hash_index(serializer);

Expand All @@ -158,6 +159,7 @@ void insert_and_get_to_and_from_hash_index(int num_keys)

{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto hash_index = setup_hash_index(serializer);

Expand Down Expand Up @@ -237,6 +239,7 @@ void insert_into_and_scan_hash_index(int num_keys)
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto hash_index = setup_hash_index(serializer);

Expand All @@ -254,6 +257,7 @@ void insert_into_and_scan_hash_index(int num_keys)

{
auto heap = SQL::Heap::construct("/tmp/test.db");
EXPECT(!heap->open().is_error());
SQL::Serializer serializer(heap);
auto hash_index = setup_hash_index(serializer);
Vector<bool> found;
Expand Down
Loading

0 comments on commit 001949d

Please sign in to comment.