From aba7f11a50f80d5d6a639639c36ce1cddf683dd5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 28 Nov 2022 07:49:03 -0500 Subject: [PATCH] LibSQL: Partially implement the DELETE command This implements enough to delete rows filtered by a WHERE clause. --- Tests/LibSQL/TestSqlStatementExecution.cpp | 118 +++++++++++++++++++++ Userland/Libraries/LibSQL/AST/AST.h | 2 + Userland/Libraries/LibSQL/AST/Delete.cpp | 39 +++++++ Userland/Libraries/LibSQL/CMakeLists.txt | 1 + Userland/Libraries/LibSQL/Database.cpp | 29 +++++ Userland/Libraries/LibSQL/Database.h | 1 + 6 files changed, 190 insertions(+) create mode 100644 Userland/Libraries/LibSQL/AST/Delete.cpp diff --git a/Tests/LibSQL/TestSqlStatementExecution.cpp b/Tests/LibSQL/TestSqlStatementExecution.cpp index c7d293755a1..c632aa408ff 100644 --- a/Tests/LibSQL/TestSqlStatementExecution.cpp +++ b/Tests/LibSQL/TestSqlStatementExecution.cpp @@ -768,4 +768,122 @@ TEST_CASE(describe_large_table_after_persist) } } +TEST_CASE(delete_single_row) +{ + ScopeGuard guard([]() { unlink(db_name); }); + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + create_table(database); + for (auto count = 0; count < 10; ++count) { + auto result = execute(database, String::formatted("INSERT INTO TestSchema.TestTable VALUES ( 'T{}', {} );", count, count)); + EXPECT_EQ(result.size(), 1u); + } + + auto result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT_EQ(result.size(), 10u); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + execute(database, "DELETE FROM TestSchema.TestTable WHERE (IntColumn = 4);"); + + auto result = execute(database, "SELECT IntColumn FROM TestSchema.TestTable ORDER BY IntColumn;"); + EXPECT_EQ(result.size(), 9u); + + for (auto i = 0u; i < 4; ++i) + EXPECT_EQ(result[i].row[0], i); + for (auto i = 5u; i < 9; ++i) + EXPECT_EQ(result[i].row[0], i + 1); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + auto result = execute(database, "SELECT IntColumn FROM TestSchema.TestTable ORDER BY IntColumn;"); + EXPECT_EQ(result.size(), 9u); + + for (auto i = 0u; i < 4; ++i) + EXPECT_EQ(result[i].row[0], i); + for (auto i = 5u; i < 9; ++i) + EXPECT_EQ(result[i].row[0], i + 1); + } +} + +TEST_CASE(delete_multiple_rows) +{ + ScopeGuard guard([]() { unlink(db_name); }); + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + create_table(database); + for (auto count = 0; count < 10; ++count) { + auto result = execute(database, String::formatted("INSERT INTO TestSchema.TestTable VALUES ( 'T{}', {} );", count, count)); + EXPECT_EQ(result.size(), 1u); + } + + auto result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT_EQ(result.size(), 10u); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + execute(database, "DELETE FROM TestSchema.TestTable WHERE (IntColumn >= 4);"); + + auto result = execute(database, "SELECT IntColumn FROM TestSchema.TestTable ORDER BY IntColumn;"); + EXPECT_EQ(result.size(), 4u); + + for (auto i = 0u; i < result.size(); ++i) + EXPECT_EQ(result[i].row[0], i); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + auto result = execute(database, "SELECT IntColumn FROM TestSchema.TestTable ORDER BY IntColumn;"); + EXPECT_EQ(result.size(), 4u); + + for (auto i = 0u; i < result.size(); ++i) + EXPECT_EQ(result[i].row[0], i); + } +} + +TEST_CASE(delete_all_rows) +{ + ScopeGuard guard([]() { unlink(db_name); }); + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + create_table(database); + for (auto count = 0; count < 10; ++count) { + auto result = execute(database, String::formatted("INSERT INTO TestSchema.TestTable VALUES ( 'T{}', {} );", count, count)); + EXPECT_EQ(result.size(), 1u); + } + + auto result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT_EQ(result.size(), 10u); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + execute(database, "DELETE FROM TestSchema.TestTable;"); + + auto result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT(result.is_empty()); + } + { + auto database = SQL::Database::construct(db_name); + EXPECT(!database->open().is_error()); + + auto result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT(result.is_empty()); + } +} + } diff --git a/Userland/Libraries/LibSQL/AST/AST.h b/Userland/Libraries/LibSQL/AST/AST.h index d9e242e97c8..155314098d5 100644 --- a/Userland/Libraries/LibSQL/AST/AST.h +++ b/Userland/Libraries/LibSQL/AST/AST.h @@ -1017,6 +1017,8 @@ public: RefPtr const& where_clause() const { return m_where_clause; } RefPtr const& returning_clause() const { return m_returning_clause; } + virtual ResultOr execute(ExecutionContext&) const override; + private: RefPtr m_common_table_expression_list; NonnullRefPtr m_qualified_table_name; diff --git a/Userland/Libraries/LibSQL/AST/Delete.cpp b/Userland/Libraries/LibSQL/AST/Delete.cpp new file mode 100644 index 00000000000..d2c87dfb6ea --- /dev/null +++ b/Userland/Libraries/LibSQL/AST/Delete.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace SQL::AST { + +ResultOr Delete::execute(ExecutionContext& context) const +{ + auto const& schema_name = m_qualified_table_name->schema_name(); + auto const& table_name = m_qualified_table_name->table_name(); + auto table_def = TRY(context.database->get_table(schema_name, table_name)); + + ResultSet result { SQLCommand::Delete }; + + for (auto& table_row : TRY(context.database->select_all(*table_def))) { + context.current_row = &table_row; + + if (auto const& where_clause = this->where_clause()) { + auto where_result = TRY(where_clause->evaluate(context)).to_bool(); + if (!where_result.has_value() || !where_result.value()) + continue; + } + + TRY(context.database->remove(table_row)); + + // FIXME: Implement the RETURNING clause. + } + + return result; +} + +} diff --git a/Userland/Libraries/LibSQL/CMakeLists.txt b/Userland/Libraries/LibSQL/CMakeLists.txt index 7b34c9b9e6a..4a726c45487 100644 --- a/Userland/Libraries/LibSQL/CMakeLists.txt +++ b/Userland/Libraries/LibSQL/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES AST/CreateSchema.cpp AST/CreateTable.cpp + AST/Delete.cpp AST/Describe.cpp AST/Expression.cpp AST/Insert.cpp diff --git a/Userland/Libraries/LibSQL/Database.cpp b/Userland/Libraries/LibSQL/Database.cpp index cb98e39b26e..92cbdd2b1e4 100644 --- a/Userland/Libraries/LibSQL/Database.cpp +++ b/Userland/Libraries/LibSQL/Database.cpp @@ -215,6 +215,35 @@ ErrorOr Database::insert(Row& row) return {}; } +ErrorOr Database::remove(Row& row) +{ + auto& table = row.table(); + VERIFY(m_table_cache.get(table.key().hash()).has_value()); + + if (table.pointer() == row.pointer()) { + auto table_key = table.key(); + table_key.set_pointer(row.next_pointer()); + m_tables->update_key_pointer(table_key); + + table.set_pointer(row.next_pointer()); + return {}; + } + + for (auto pointer = table.pointer(); pointer;) { + auto current = m_serializer.deserialize_block(pointer, table, pointer); + + if (current.next_pointer() == row.pointer()) { + current.set_next_pointer(row.next_pointer()); + TRY(update(current)); + break; + } + + pointer = current.next_pointer(); + } + + return {}; +} + ErrorOr Database::update(Row& tuple) { VERIFY(m_table_cache.get(tuple.table().key().hash()).has_value()); diff --git a/Userland/Libraries/LibSQL/Database.h b/Userland/Libraries/LibSQL/Database.h index baf7ebb7098..727f16db576 100644 --- a/Userland/Libraries/LibSQL/Database.h +++ b/Userland/Libraries/LibSQL/Database.h @@ -44,6 +44,7 @@ public: ErrorOr> select_all(TableDef const&); ErrorOr> match(TableDef const&, Key const&); ErrorOr insert(Row&); + ErrorOr remove(Row&); ErrorOr update(Row&); private: