diff --git a/Tests/LibSQL/TestSqlStatementExecution.cpp b/Tests/LibSQL/TestSqlStatementExecution.cpp index c7d293755a..c632aa408f 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 d9e242e97c..155314098d 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 0000000000..d2c87dfb6e --- /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 7b34c9b9e6..4a726c4548 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 cb98e39b26..92cbdd2b1e 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 baf7ebb709..727f16db57 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: