ladybird/Userland/Libraries/LibSQL/AST/Select.cpp
Timothy Flynn f3c6cb40d7 LibSQL: Convert SQL expression evaluation to use ResultOr
Instead of setting an error in the execution context, we can directly
return that error or the successful value. This lets all callers, who
were already TRY-capable, simply TRY the expression evaluation.
2022-02-10 23:11:13 +01:00

149 lines
5.1 KiB
C++

/*
* Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NumericLimits.h>
#include <LibSQL/AST/AST.h>
#include <LibSQL/Database.h>
#include <LibSQL/Meta.h>
#include <LibSQL/Row.h>
namespace SQL::AST {
ResultOr<ResultSet> Select::execute(ExecutionContext& context) const
{
NonnullRefPtrVector<ResultColumn> columns;
auto const& result_column_list = this->result_column_list();
VERIFY(!result_column_list.is_empty());
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor.is_table())
return Result { SQLCommand::Select, SQLErrorCode::NotYetImplemented, "Sub-selects are not yet implemented"sv };
auto table_def = TRY(context.database->get_table(table_descriptor.schema_name(), table_descriptor.table_name()));
if (!table_def)
return Result { SQLCommand::Select, SQLErrorCode::TableDoesNotExist, table_descriptor.table_name() };
if (result_column_list.size() == 1 && result_column_list[0].type() == ResultType::All) {
for (auto& col : table_def->columns()) {
columns.append(
create_ast_node<ResultColumn>(
create_ast_node<ColumnNameExpression>(table_def->parent()->name(), table_def->name(), col.name()),
""));
}
}
}
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 Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "*"sv };
}
columns.append(col);
}
}
context.result = Result { SQLCommand::Select };
ResultSet result { SQLCommand::Select };
auto descriptor = adopt_ref(*new TupleDescriptor);
Tuple tuple(descriptor);
Vector<Tuple> rows;
descriptor->empend("__unity__"sv);
tuple.append(Value(SQLType::Boolean, true));
rows.append(tuple);
for (auto& table_descriptor : table_or_subquery_list()) {
if (!table_descriptor.is_table())
return Result { SQLCommand::Select, SQLErrorCode::NotYetImplemented, "Sub-selects are not yet implemented"sv };
auto table_def = TRY(context.database->get_table(table_descriptor.schema_name(), table_descriptor.table_name()));
if (table_def->num_columns() == 0)
continue;
auto old_descriptor_size = descriptor->size();
descriptor->extend(table_def->to_tuple_descriptor());
while (!rows.is_empty() && (rows.first().size() == old_descriptor_size)) {
auto cartesian_row = rows.take_first();
auto table_rows = TRY(context.database->select_all(*table_def));
for (auto& table_row : table_rows) {
auto new_row = cartesian_row;
new_row.extend(table_row);
rows.append(new_row);
}
}
}
bool has_ordering { false };
auto sort_descriptor = adopt_ref(*new TupleDescriptor);
for (auto& term : m_ordering_term_list) {
sort_descriptor->append(TupleElementDescriptor { .order = term.order() });
has_ordering = true;
}
Tuple sort_key(sort_descriptor);
for (auto& row : rows) {
context.current_row = &row;
if (where_clause()) {
auto where_result = TRY(where_clause()->evaluate(context));
if (!where_result)
continue;
}
tuple.clear();
for (auto& col : columns) {
auto value = TRY(col.expression()->evaluate(context));
tuple.append(value);
}
if (has_ordering) {
sort_key.clear();
for (auto& term : m_ordering_term_list) {
auto value = TRY(term.expression()->evaluate(context));
sort_key.append(value);
}
}
result.insert_row(tuple, sort_key);
}
if (m_limit_clause != nullptr) {
size_t limit_value = NumericLimits<size_t>::max();
size_t offset_value = 0;
auto limit = TRY(m_limit_clause->limit_expression()->evaluate(context));
if (!limit.is_null()) {
auto limit_value_maybe = limit.to_u32();
if (!limit_value_maybe.has_value())
return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "LIMIT clause must evaluate to an integer value"sv };
limit_value = limit_value_maybe.value();
}
if (m_limit_clause->offset_expression() != nullptr) {
auto offset = TRY(m_limit_clause->offset_expression()->evaluate(context));
if (!offset.is_null()) {
auto offset_value_maybe = offset.to_u32();
if (!offset_value_maybe.has_value())
return Result { SQLCommand::Select, SQLErrorCode::SyntaxError, "OFFSET clause must evaluate to an integer value"sv };
offset_value = offset_value_maybe.value();
}
}
result.limit(offset_value, limit_value);
}
return result;
}
}