ladybird/Userland/Libraries/LibSQL/AST/Select.cpp
Jan de Visser 3425730294 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.
2021-11-10 14:47:49 +01:00

91 lines
3.3 KiB
C++

/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
RefPtr<SQLResult> Select::execute(ExecutionContext& context) const
{
NonnullRefPtrVector<ResultColumn> columns;
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor.is_table())
TODO();
auto table = context.database->get_table(table_descriptor.schema_name(), table_descriptor.table_name());
if (!table) {
return SQLResult::construct(SQL::SQLCommand::Select, SQL::SQLErrorCode::TableDoesNotExist, table_descriptor.table_name());
}
if (result_column_list().size() == 1 && result_column_list()[0].type() == ResultType::All) {
for (auto& col : table->columns()) {
columns.append(
create_ast_node<ResultColumn>(
create_ast_node<ColumnNameExpression>(table->parent()->name(), table->name(), col.name()),
""));
}
}
}
VERIFY(!result_column_list().is_empty());
if (result_column_list().size() != 1 || result_column_list()[0].type() != ResultType::All) {
for (auto& col : result_column_list()) {
if (col.type() == ResultType::All)
// FIXME can have '*' for example in conjunction with computed columns
return SQLResult::construct(SQL::SQLCommand::Select, SQL::SQLErrorCode::SyntaxError, "*");
columns.append(col);
}
}
context.result = SQLResult::construct();
AK::NonnullRefPtr<TupleDescriptor> descriptor = AK::adopt_ref(*new TupleDescriptor);
Tuple tuple(descriptor);
Vector<Tuple> rows;
descriptor->empend("__unity__");
tuple.append(Value(SQLType::Boolean, true));
rows.append(tuple);
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor.is_table())
TODO();
auto table = context.database->get_table(table_descriptor.schema_name(), table_descriptor.table_name());
if (table->num_columns() == 0)
continue;
auto old_descriptor_size = descriptor->size();
descriptor->extend(table->to_tuple_descriptor());
for (auto cartesian_row = rows.first(); cartesian_row.size() == old_descriptor_size; cartesian_row = rows.first()) {
rows.remove(0);
for (auto& table_row : context.database->select_all(*table)) {
auto new_row = cartesian_row;
new_row.extend(table_row);
rows.append(new_row);
}
}
}
for (auto& row : rows) {
context.current_row = &row;
if (where_clause()) {
auto where_result = where_clause()->evaluate(context);
if (context.result->has_error())
return context.result;
if (!where_result)
continue;
}
tuple.clear();
for (auto& col : columns) {
auto value = col.expression()->evaluate(context);
if (context.result->has_error())
return context.result;
tuple.append(value);
}
context.result->append(tuple);
}
return context.result;
}
}