diff --git a/Tests/LibSQL/TestSqlStatementExecution.cpp b/Tests/LibSQL/TestSqlStatementExecution.cpp new file mode 100644 index 0000000000..cd9a35f2d9 --- /dev/null +++ b/Tests/LibSQL/TestSqlStatementExecution.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr const char* db_name = "/tmp/test.db"; + +RefPtr execute(NonnullRefPtr database, String const& sql) +{ + auto parser = SQL::AST::Parser(SQL::AST::Lexer(sql)); + auto statement = parser.next_statement(); + EXPECT(!parser.has_errors()); + if (parser.has_errors()) { + outln(parser.errors()[0].to_string()); + } + SQL::AST::ExecutionContext context { database }; + auto result = statement->execute(context); + EXPECT(result->error().code == SQL::SQLErrorCode::NoError); + return result; +} + +void create_schema(NonnullRefPtr database) +{ + auto result = execute(database, "CREATE SCHEMA TestSchema;"); + EXPECT(result->inserted() == 1); +} + +void create_table(NonnullRefPtr database) +{ + create_schema(database); + auto result = execute(database, "CREATE TABLE TestSchema.TestTable ( TextColumn text, IntColumn integer );"); + EXPECT(result->inserted() == 1); +} + +TEST_CASE(create_schema) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_schema(database); + auto schema = database->get_schema("TESTSCHEMA"); + EXPECT(schema); +} + +TEST_CASE(create_table) +{ + ScopeGuard guard([]() { unlink(db_name); }); + auto database = SQL::Database::construct(db_name); + create_table(database); + auto table = database->get_table("TESTSCHEMA", "TESTTABLE"); + EXPECT(table); +} + +TEST_CASE(insert_into_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', 42 );"); + EXPECT(result->inserted() == 1); + + auto table = database->get_table("TESTSCHEMA", "TESTTABLE"); + + int count = 0; + for (auto& row : database->select_all(*table)) { + EXPECT_EQ(row["TEXTCOLUMN"].to_string(), "Test"); + EXPECT_EQ(row["INTCOLUMN"].to_int().value(), 42); + count++; + } + EXPECT_EQ(count, 1); +} + +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 );"); + EXPECT(result->inserted() == 2); + result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_3', 44 ), ( 'Test_4', 45 );"); + EXPECT(result->inserted() == 2); + result = execute(database, "INSERT INTO TestSchema.TestTable ( TextColumn, IntColumn ) VALUES ( 'Test_5', 46 );"); + EXPECT(result->inserted() == 1); + result = execute(database, "SELECT * FROM TestSchema.TestTable;"); + EXPECT(result->has_results()); + EXPECT_EQ(result->results().size(), 5u); +} + +} diff --git a/Userland/Libraries/LibSQL/AST/AST.h b/Userland/Libraries/LibSQL/AST/AST.h index cd9001cf31..7a8ca428eb 100644 --- a/Userland/Libraries/LibSQL/AST/AST.h +++ b/Userland/Libraries/LibSQL/AST/AST.h @@ -295,7 +295,15 @@ private: // Expressions //================================================================================================== +struct ExecutionContext { + NonnullRefPtr database; + RefPtr result { nullptr }; + Tuple current_row {}; +}; + class Expression : public ASTNode { +public: + virtual Value evaluate(ExecutionContext&) const; }; class ErrorExpression final : public Expression { @@ -309,6 +317,7 @@ public: } double value() const { return m_value; } + virtual Value evaluate(ExecutionContext&) const override; private: double m_value; @@ -322,6 +331,7 @@ public: } const String& value() const { return m_value; } + virtual Value evaluate(ExecutionContext&) const override; private: String m_value; @@ -341,11 +351,14 @@ private: }; class NullLiteral : public Expression { +public: + virtual Value evaluate(ExecutionContext&) const override; }; class NestedExpression : public Expression { public: const NonnullRefPtr& expression() const { return m_expression; } + virtual Value evaluate(ExecutionContext&) const override; protected: explicit NestedExpression(NonnullRefPtr expression) @@ -439,6 +452,7 @@ public: } UnaryOperator type() const { return m_type; } + virtual Value evaluate(ExecutionContext&) const override; private: UnaryOperator m_type; @@ -488,6 +502,7 @@ public: } const NonnullRefPtrVector& expressions() const { return m_expressions; } + virtual Value evaluate(ExecutionContext&) const override; private: NonnullRefPtrVector m_expressions; @@ -667,7 +682,7 @@ private: class Statement : public ASTNode { public: - virtual RefPtr execute(NonnullRefPtr) const { return nullptr; } + virtual RefPtr execute(ExecutionContext&) const { return nullptr; } }; class ErrorStatement final : public Statement { @@ -684,7 +699,7 @@ public: const String& schema_name() const { return m_schema_name; } bool is_error_if_schema_exists() const { return m_is_error_if_schema_exists; } - RefPtr execute(NonnullRefPtr) const override; + RefPtr execute(ExecutionContext&) const override; private: String m_schema_name; @@ -723,7 +738,7 @@ public: bool is_temporary() const { return m_is_temporary; } bool is_error_if_table_exists() const { return m_is_error_if_table_exists; } - RefPtr execute(NonnullRefPtr) const override; + RefPtr execute(ExecutionContext&) const override; private: String m_schema_name; @@ -886,6 +901,8 @@ public: bool has_selection() const { return !m_select_statement.is_null(); } const RefPtr