1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14:38:11 +00:00

LibSQL: Implement table joins

This patch introduces table joins. It uses a pretty dumb algorithm-
starting with a singleton '__unity__' row consisting of a single boolean
value, a cartesian product of all tables in the 'FROM' clause is built.
This cartesian product is then filtered through the 'WHERE' clause,
again without any smarts just using brute force.

This patch required a bunch of busy work to allow for example the
ColumnNameExpression having to deal with multiple tables potentially
having columns with the same name.
This commit is contained in:
Jan de Visser 2021-11-02 16:49:54 -04:00 committed by Andreas Kling
parent 87f4c1677b
commit 3425730294
4 changed files with 175 additions and 50 deletions

View file

@ -46,6 +46,17 @@ void create_table(NonnullRefPtr<SQL::Database> database)
EXPECT(result->inserted() == 1);
}
void create_two_tables(NonnullRefPtr<SQL::Database> database)
{
create_schema(database);
auto result = execute(database, "CREATE TABLE TestSchema.TestTable1 ( TextColumn1 text, IntColumn integer );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 1);
result = execute(database, "CREATE TABLE TestSchema.TestTable2 ( TextColumn2 text, IntColumn integer );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 1);
}
TEST_CASE(create_schema)
{
ScopeGuard guard([]() { unlink(db_name); });
@ -132,15 +143,15 @@ TEST_CASE(select_from_table)
ScopeGuard guard([]() { unlink(db_name); });
auto database = SQL::Database::construct(db_name);
create_table(database);
auto result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_1', 42 ), ( 'Test_2', 43 );");
auto result = execute(database,
"INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES "
"( 'Test_1', 42 ), "
"( 'Test_2', 43 ), "
"( 'Test_3', 44 ), "
"( 'Test_4', 45 ), "
"( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 2);
result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_3', 44 ), ( 'Test_4', 45 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 2);
result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 1);
EXPECT(result->inserted() == 5);
result = execute(database, "SELECT * FROM TestSchema.TestTable;");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->has_results());
@ -152,15 +163,15 @@ TEST_CASE(select_with_column_names)
ScopeGuard guard([]() { unlink(db_name); });
auto database = SQL::Database::construct(db_name);
create_table(database);
auto result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_1', 42 ), ( 'Test_2', 43 );");
auto result = execute(database,
"INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES "
"( 'Test_1', 42 ), "
"( 'Test_2', 43 ), "
"( 'Test_3', 44 ), "
"( 'Test_4', 45 ), "
"( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 2);
result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_3', 44 ), ( 'Test_4', 45 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 2);
result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 1);
EXPECT(result->inserted() == 5);
result = execute(database, "SELECT TextColumn FROM TestSchema.TestTable;");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->has_results());
@ -209,4 +220,77 @@ TEST_CASE(select_with_where)
}
}
TEST_CASE(select_cross_join)
{
ScopeGuard guard([]() { unlink(db_name); });
auto database = SQL::Database::construct(db_name);
create_two_tables(database);
auto result = execute(database,
"INSERT INTO TestSchema.TestTable1 ( TextColumn1, IntColumn ) VALUES "
"( 'Test_1', 42 ), "
"( 'Test_2', 43 ), "
"( 'Test_3', 44 ), "
"( 'Test_4', 45 ), "
"( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 5);
result = execute(database,
"INSERT INTO TestSchema.TestTable2 ( TextColumn2, IntColumn ) VALUES "
"( 'Test_10', 40 ), "
"( 'Test_11', 41 ), "
"( 'Test_12', 42 ), "
"( 'Test_13', 47 ), "
"( 'Test_14', 48 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 5);
result = execute(database, "SELECT * FROM TestSchema.TestTable1, TestSchema.TestTable2;");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->has_results());
EXPECT_EQ(result->results().size(), 25u);
for (auto& row : result->results()) {
EXPECT(row.size() == 4);
EXPECT(row[1].to_int().value() >= 42);
EXPECT(row[1].to_int().value() <= 46);
EXPECT(row[3].to_int().value() >= 40);
EXPECT(row[3].to_int().value() <= 48);
}
}
TEST_CASE(select_inner_join)
{
ScopeGuard guard([]() { unlink(db_name); });
auto database = SQL::Database::construct(db_name);
create_two_tables(database);
auto result = execute(database,
"INSERT INTO TestSchema.TestTable1 ( TextColumn1, IntColumn ) VALUES "
"( 'Test_1', 42 ), "
"( 'Test_2', 43 ), "
"( 'Test_3', 44 ), "
"( 'Test_4', 45 ), "
"( 'Test_5', 46 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 5);
result = execute(database,
"INSERT INTO TestSchema.TestTable2 ( TextColumn2, IntColumn ) VALUES "
"( 'Test_10', 40 ), "
"( 'Test_11', 41 ), "
"( 'Test_12', 42 ), "
"( 'Test_13', 47 ), "
"( 'Test_14', 48 );");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->inserted() == 5);
result = execute(database,
"SELECT TestTable1.IntColumn, TextColumn1, TextColumn2 "
"FROM TestSchema.TestTable1, TestSchema.TestTable2 "
"WHERE TestTable1.IntColumn = TestTable2.IntColumn;");
EXPECT(result->error().code == SQL::SQLErrorCode::NoError);
EXPECT(result->has_results());
EXPECT_EQ(result->results().size(), 1u);
auto& row = result->results()[0];
EXPECT_EQ(row.size(), 3u);
EXPECT_EQ(row[0].to_int().value(), 42);
EXPECT_EQ(row[1].to_string(), "Test_1");
EXPECT_EQ(row[2].to_string(), "Test_12");
}
}