2021-06-17 14:12:46 -04:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
|
2023-05-24 12:29:21 +02:00
|
|
|
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
|
2021-06-17 14:12:46 -04:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <AK/ScopeGuard.h>
|
2023-05-24 12:29:21 +02:00
|
|
|
#include <AK/StringBuilder.h>
|
|
|
|
|
#include <LibCore/System.h>
|
2021-06-17 14:12:46 -04:00
|
|
|
#include <LibSQL/BTree.h>
|
|
|
|
|
#include <LibSQL/Database.h>
|
|
|
|
|
#include <LibSQL/Heap.h>
|
|
|
|
|
#include <LibSQL/Meta.h>
|
|
|
|
|
#include <LibSQL/Row.h>
|
|
|
|
|
#include <LibSQL/Value.h>
|
|
|
|
|
#include <LibTest/TestCase.h>
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
static NonnullRefPtr<SQL::SchemaDef> setup_schema(SQL::Database& db)
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
|
|
|
|
auto schema = SQL::SchemaDef::construct("TestSchema");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db.add_schema(schema));
|
2021-06-17 14:12:46 -04:00
|
|
|
return schema;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
// FIXME: using the return value for SQL::TableDef to insert a row results in a segfault
|
|
|
|
|
static NonnullRefPtr<SQL::TableDef> setup_table(SQL::Database& db)
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
|
|
|
|
auto schema = setup_schema(db);
|
|
|
|
|
auto table = SQL::TableDef::construct(schema, "TestTable");
|
|
|
|
|
table->append_column("TextColumn", SQL::SQLType::Text);
|
|
|
|
|
table->append_column("IntColumn", SQL::SQLType::Integer);
|
|
|
|
|
EXPECT_EQ(table->num_columns(), 2u);
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db.add_table(table));
|
2021-06-17 14:12:46 -04:00
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
static void insert_into_table(SQL::Database& db, int count)
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2022-11-29 08:47:22 -05:00
|
|
|
auto table = MUST(db.get_table("TestSchema", "TestTable"));
|
2021-06-17 14:12:46 -04:00
|
|
|
|
|
|
|
|
for (int ix = 0; ix < count; ix++) {
|
|
|
|
|
SQL::Row row(*table);
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
builder.appendff("Test{}", ix);
|
|
|
|
|
|
2023-01-26 18:58:09 +00:00
|
|
|
row["TextColumn"] = builder.to_deprecated_string();
|
2021-06-17 14:12:46 -04:00
|
|
|
row["IntColumn"] = ix;
|
2023-05-07 20:14:06 +02:00
|
|
|
TRY_OR_FAIL(db.insert(row));
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
static void verify_table_contents(SQL::Database& db, int expected_count)
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2022-11-29 08:47:22 -05:00
|
|
|
auto table = MUST(db.get_table("TestSchema", "TestTable"));
|
2021-06-17 14:12:46 -04:00
|
|
|
|
|
|
|
|
int sum = 0;
|
|
|
|
|
int count = 0;
|
2023-05-07 20:14:06 +02:00
|
|
|
auto rows = TRY_OR_FAIL(db.select_all(*table));
|
|
|
|
|
for (auto& row : rows) {
|
2021-06-17 14:12:46 -04:00
|
|
|
StringBuilder builder;
|
LibSQL: Support 64-bit integer values and handle overflow errors
Currently, integers are stored in LibSQL as 32-bit signed integers, even
if the provided type is unsigned. This resulted in a series of unchecked
unsigned-to-signed conversions, and prevented storing 64-bit values.
Further, mathematical operations were performed without similar checks,
and without checking for overflow.
This changes SQL::Value to behave like SQLite for INTEGER types. In
SQLite, the INTEGER type does not imply a size or signedness of the
underlying type. Instead, SQLite determines on-the-fly what type is
needed as values are created and updated.
To do so, the SQL::Value variant can now hold an i64 or u64 integer. If
a specific type is requested, invalid conversions are now explictly an
error (e.g. converting a stored -1 to a u64 will fail). When binary
mathematical operations are performed, we now try to coerce the RHS
value to a type that works with the LHS value, failing the operation if
that isn't possible. Any overflow or invalid operation (e.g. bitshifting
a 64-bit value by more than 64 bytes) is an error.
2022-12-11 11:44:11 -05:00
|
|
|
builder.appendff("Test{}", row["IntColumn"].to_int<i32>().value());
|
2023-01-26 18:58:09 +00:00
|
|
|
EXPECT_EQ(row["TextColumn"].to_deprecated_string(), builder.to_deprecated_string());
|
2021-06-17 14:12:46 -04:00
|
|
|
count++;
|
LibSQL: Support 64-bit integer values and handle overflow errors
Currently, integers are stored in LibSQL as 32-bit signed integers, even
if the provided type is unsigned. This resulted in a series of unchecked
unsigned-to-signed conversions, and prevented storing 64-bit values.
Further, mathematical operations were performed without similar checks,
and without checking for overflow.
This changes SQL::Value to behave like SQLite for INTEGER types. In
SQLite, the INTEGER type does not imply a size or signedness of the
underlying type. Instead, SQLite determines on-the-fly what type is
needed as values are created and updated.
To do so, the SQL::Value variant can now hold an i64 or u64 integer. If
a specific type is requested, invalid conversions are now explictly an
error (e.g. converting a stored -1 to a u64 will fail). When binary
mathematical operations are performed, we now try to coerce the RHS
value to a type that works with the LHS value, failing the operation if
that isn't possible. Any overflow or invalid operation (e.g. bitshifting
a 64-bit value by more than 64 bytes) is an error.
2022-12-11 11:44:11 -05:00
|
|
|
sum += row["IntColumn"].to_int<i32>().value();
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
EXPECT_EQ(count, expected_count);
|
|
|
|
|
EXPECT_EQ(sum, (expected_count * (expected_count - 1)) / 2);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
static void commit(SQL::Database& db)
|
2021-11-05 19:05:59 -04:00
|
|
|
{
|
2023-05-07 20:14:06 +02:00
|
|
|
TRY_OR_FAIL(db.commit());
|
2021-11-05 19:05:59 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-24 12:29:21 +02:00
|
|
|
static void insert_and_verify(int count)
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-12-02 11:00:10 +00:00
|
|
|
(void)setup_table(db);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-06-17 14:12:46 -04:00
|
|
|
insert_into_table(db, count);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-06-17 14:12:46 -04:00
|
|
|
verify_table_contents(db, count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(create_heap)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
|
|
|
|
auto heap = SQL::Heap::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
TRY_OR_FAIL(heap->open());
|
2023-04-19 19:10:31 +02:00
|
|
|
EXPECT_EQ(heap->version(), SQL::Heap::VERSION);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-05 19:05:59 -04:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 14:12:46 -04:00
|
|
|
TEST_CASE(create_database)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
|
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(add_schema_to_database)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
|
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-12-02 11:00:10 +00:00
|
|
|
(void)setup_schema(db);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(get_schema_from_database)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-12-02 11:00:10 +00:00
|
|
|
(void)setup_schema(db);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
|
|
|
|
auto schema = MUST(db->get_schema("TestSchema"));
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(add_table_to_database)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
|
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-12-02 11:00:10 +00:00
|
|
|
(void)setup_table(db);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(get_table_from_database)
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
ScopeGuard guard([]() { unlink("/tmp/test.db"); });
|
2021-06-17 14:12:46 -04:00
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2021-12-02 11:00:10 +00:00
|
|
|
(void)setup_table(db);
|
2021-11-05 19:05:59 -04:00
|
|
|
commit(db);
|
2021-06-17 14:12:46 -04:00
|
|
|
}
|
|
|
|
|
{
|
2021-06-22 10:03:59 -03:00
|
|
|
auto db = SQL::Database::construct("/tmp/test.db");
|
2023-05-07 20:14:06 +02:00
|
|
|
MUST(db->open());
|
2022-11-29 08:47:22 -05:00
|
|
|
|
|
|
|
|
auto table = MUST(db->get_table("TestSchema", "TestTable"));
|
2021-06-17 14:12:46 -04:00
|
|
|
EXPECT_EQ(table->name(), "TestTable");
|
|
|
|
|
EXPECT_EQ(table->num_columns(), 2u);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insert_one_into_and_select_from_table)
|
|
|
|
|
{
|
|
|
|
|
insert_and_verify(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insert_two_into_table)
|
|
|
|
|
{
|
|
|
|
|
insert_and_verify(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insert_10_into_table)
|
|
|
|
|
{
|
|
|
|
|
insert_and_verify(10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insert_100_into_table)
|
|
|
|
|
{
|
|
|
|
|
insert_and_verify(100);
|
|
|
|
|
}
|